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

Added documentation in the user guide for delta restores, expiration, dedicated backup hosts, starting and stopping pgBackRest, and replication.

This commit is contained in:
David Steele 2015-11-22 16:44:01 -05:00
parent 8ddfdcdd3b
commit fa05715dec
33 changed files with 10520 additions and 1263 deletions

View File

@ -3,6 +3,8 @@
## v0.90: UNDER DEVELOPMENT
__No Release Date Set__
* Added documentation in the user guide for delta restores, expiration, dedicated backup hosts, starting and stopping pgBackRest, and replication.
* Fixed an issue where the `start`/`stop` commands required the `--config` option.
* Fixed an issue where log files were being overwritten instead of appended.

View File

@ -16,18 +16,22 @@ use Cwd qw(abs_path);
use File::Basename qw(dirname);
use Getopt::Long qw(GetOptions);
use Pod::Usage qw(pod2usage);
use Storable;
use XML::Checker::Parser;
use lib dirname($0) . '/lib';
use BackRestDoc::Common::Doc;
use BackRestDoc::Common::DocConfig;
use BackRestDoc::Html::DocHtmlSite;
use BackRestDoc::Common::DocManifest;
use BackRestDoc::Common::DocRender;
use BackRestDoc::Html::DocHtmlSite;
use BackRestDoc::Latex::DocLatex;
use lib dirname($0) . '/../lib';
use BackRest::Common::Log;
use BackRest::Common::String;
use BackRest::Config::Config;
use BackRest::FileCommon;
####################################################################################################################################
# Usage
@ -60,19 +64,25 @@ my $bHelp = false; # Display usage
my $bVersion = false; # Display version
my $bQuiet = false; # Sets log level to ERROR
my $strLogLevel = 'info'; # Log level for tests
my $strProjectName = 'pgBackRest'; # Project name to use in docs
my $strExeName = 'pg_backrest'; # Exe name to use in docs
my $bHtml = false; # Generate full html documentation
my $strHtmlRoot = '/'; # Root html page
my $bNoExe = false; # Should commands be executed when buildng help? (for testing only)
my $bUseCache = false; # Use cached data to generate the docs (for testing code changes only)
my $oVariableOverride = {}; # Override variables
my $strDocPath; # Document path to render
my @stryOutput; # Output types
my @stryKeyword; # Keyword used to filter output
my @stryRequire; # Required sections of the document (to speed testing)
GetOptions ('help' => \$bHelp,
'version' => \$bVersion,
'quiet' => \$bQuiet,
'log-level=s' => \$strLogLevel,
'html' => \$bHtml,
'html-root=s' => \$strHtmlRoot,
'no-exe', \$bNoExe)
'out=s@' => \@stryOutput,
'keyword=s@' => \@stryKeyword,
'require=s@' => \@stryRequire,
'no-exe', \$bNoExe,
'use-cache', \$bUseCache,
'var=s%', $oVariableOverride,
'doc-path=s', \$strDocPath)
or pod2usage(2);
# Display version and exit if requested
@ -89,16 +99,29 @@ if ($bHelp || $bVersion)
exit 0;
}
# Set no-exe if use-cache is set
if ($bUseCache)
{
$bNoExe = true;
}
# Set console log level
if ($bQuiet)
{
$strLogLevel = 'off';
$strLogLevel = 'error';
}
# If no keyword we passed then use default
if (@stryKeyword == 0)
{
@stryKeyword = ('default')
}
logLevelSet(undef, uc($strLogLevel));
# Get the base path
my $strBasePath = abs_path(dirname($0));
my $strOutputPath = "${strBasePath}/output";
sub docProcess
{
@ -108,7 +131,7 @@ sub docProcess
$strOperation,
$strXmlIn,
$strMdOut,
$oHtmlSite
$oManifest
) =
logDebugParam
(
@ -122,33 +145,98 @@ sub docProcess
my $oDoc = new BackRestDoc::Common::Doc($strXmlIn);
# Write markdown
my $oRender = new BackRestDoc::Common::DocRender('markdown', $strProjectName, $strExeName);
$oRender->save($strMdOut, $oHtmlSite->variableReplace($oRender->process($oDoc)));
my $oRender = new BackRestDoc::Common::DocRender('markdown', $oManifest);
$oRender->save($strMdOut, $oManifest->variableReplace($oRender->process($oDoc)));
}
my $oRender = new BackRestDoc::Common::DocRender('text', $strProjectName, $strExeName);
my $oDocConfig = new BackRestDoc::Common::DocConfig(new BackRestDoc::Common::Doc("${strBasePath}/xml/reference.xml"), $oRender);
$oDocConfig->helpDataWrite();
# !!! Create Html Site Object to perform variable replacements on markdown and test
# !!! This should be replaced with a more generic site object in the future
my $oHtmlSite =
new BackRestDoc::Html::DocHtmlSite
(
new BackRestDoc::Common::DocRender('html', $strProjectName, $strExeName),
$oDocConfig,
"${strBasePath}/xml",
"${strBasePath}/html",
"${strBasePath}/css/default.css",
$strHtmlRoot,
!$bNoExe
);
docProcess("${strBasePath}/xml/index.xml", "${strBasePath}/../README.md", $oHtmlSite);
docProcess("${strBasePath}/xml/change-log.xml", "${strBasePath}/../CHANGELOG.md", $oHtmlSite);
# Only generate the HTML site when requested
if ($bHtml)
# Create the out path if it does not exist
if (!-e $strOutputPath)
{
$oHtmlSite->process();
mkdir($strOutputPath)
or confess &log(ERROR, "unable to create path ${strOutputPath}");
}
# Load the manifest
my $oManifest;
my $strManifestCache = "${strOutputPath}/manifest.cache";
if ($bUseCache && -e $strManifestCache)
{
$oManifest = retrieve($strManifestCache);
}
else
{
$oManifest = new BackRestDoc::Common::DocManifest(\@stryKeyword, \@stryRequire, $oVariableOverride, $strDocPath);
}
# If no outputs were given
if (@stryOutput == 0)
{
@stryOutput = $oManifest->renderList();
if ($oManifest->isBackRest())
{
push(@stryOutput, 'help');
push(@stryOutput, 'markdown');
}
}
for my $strOutput (@stryOutput)
{
if (!(($strOutput eq 'help' || $strOutput eq 'markdown') && $oManifest->isBackRest()))
{
$oManifest->renderGet($strOutput);
}
&log(INFO, "render ${strOutput} output");
if ($strOutput eq 'markdown' && $oManifest->isBackRest())
{
# Generate the markdown
docProcess("${strBasePath}/xml/index.xml", "${strBasePath}/../README.md", $oManifest);
docProcess("${strBasePath}/xml/change-log.xml", "${strBasePath}/../CHANGELOG.md", $oManifest);
}
elsif ($strOutput eq 'help' && $oManifest->isBackRest())
{
# Generate the command-line help
my $oRender = new BackRestDoc::Common::DocRender('text', $oManifest);
my $oDocConfig =
new BackRestDoc::Common::DocConfig(
new BackRestDoc::Common::Doc("${strBasePath}/xml/reference.xml"), $oRender);
$oDocConfig->helpDataWrite($oManifest);
}
elsif ($strOutput eq 'html')
{
my $oHtmlSite =
new BackRestDoc::Html::DocHtmlSite
(
$oManifest,
"${strBasePath}/xml",
"${strOutputPath}/html",
"${strBasePath}/resource/html/default.css",
!$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 (!$bUseCache)
{
store($oManifest, $strManifestCache);
}

View File

@ -15,6 +15,7 @@ use Scalar::Util qw(blessed);
use lib dirname($0) . '/../lib';
use BackRest::Common::Log;
use BackRest::Common::String;
use BackRest::FileCommon;
####################################################################################################################################
# Operation constants
@ -27,8 +28,10 @@ use constant OP_DOC_NEW => OP_DOC .
use constant OP_DOC_NODE_BLESS => OP_DOC . '->nodeBless';
use constant OP_DOC_NODE_GET => OP_DOC . '->nodeGet';
use constant OP_DOC_NODE_LIST => OP_DOC . '->nodeList';
use constant OP_DOC_NODE_REMOVE => OP_DOC . '->nodeRemove';
use constant OP_DOC_PARAM_GET => OP_DOC . '->paramGet';
use constant OP_DOC_PARAM_SET => OP_DOC . '->paramSet';
use constant OP_DOC_PARAM_TEST => OP_DOC . '->paramSet';
use constant OP_DOC_PARSE => OP_DOC . '->parse';
use constant OP_DOC_VALUE_GET => OP_DOC . '->valueGet';
use constant OP_DOC_VALUE_SET => OP_DOC . '->valueSet';
@ -49,44 +52,60 @@ sub new
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{strFileName}
$self->{strFileName},
my $bCached
) =
logDebugParam
(
OP_DOC_NEW, \@_,
{name => 'strFileName', required => false}
{name => 'strFileName', required => false},
{name => 'bCached', default => 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(dirname($self->{strFileName}) . '/dtd');
my $oTree;
eval
if ($bCached)
{
local $XML::Checker::FAIL = sub
{
my $iCode = shift;
$self->oDoc = XMLin(fileStringRead($self->{strFileName}));
}
else
{
my $oParser = XML::Checker::Parser->new(ErrorContext => 2, Style => 'Tree');
die XML::Checker::error_string($iCode, @_);
if (-e dirname($0) . '/dtd')
{
$oParser->set_sgml_search_path(dirname($0) . '/dtd')
}
else
{
$oParser->set_sgml_search_path(dirname($0) . '/xml/dtd');
}
my $oTree;
eval
{
local $XML::Checker::FAIL = sub
{
my $iCode = shift;
die XML::Checker::error_string($iCode, @_);
};
$oTree = $oParser->parsefile($self->{strFileName});
};
$oTree = $oParser->parsefile($self->{strFileName});
};
# Report any error that stopped parsing
if ($@)
{
$@ =~ s/at \/.*?$//s; # remove module line number
die "malformed xml in '$self->{strFileName}':\n" . trim($@);
}
# Report any error that stopped parsing
if ($@)
{
$@ =~ s/at \/.*?$//s; # remove module line number
die "malformed xml in '$self->{strFileName}':\n" . trim($@);
# Parse and build the doc
$self->{oDoc} = $self->build($self->parse(${$oTree}[0], ${$oTree}[1]));
}
# Parse and build the doc
$self->{oDoc} = $self->build($self->parse(${$oTree}[0], ${$oTree}[1]));
}
# Else create a blank doc
else
@ -129,8 +148,8 @@ sub parse
my %oOut;
my $iIndex = 0;
my $bText = $strName eq 'text' || $strName eq 'li' || $strName eq 'code-block' || $strName eq 'p' || $strName eq 'title' ||
$strName eq 'summary';
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';
# Store the node name
$oOut{name} = $strName;
@ -244,7 +263,8 @@ sub build
}
}
if ($$oDoc{name} eq 'p' || $$oDoc{name} eq 'title' || $$oDoc{name} eq 'summary')
if ($$oDoc{name} eq 'p' || $$oDoc{name} eq 'title' || $$oDoc{name} eq 'summary' ||
$$oDoc{name} eq 'table-cell' || $$oDoc{name} eq 'table-column')
{
$$oOut{field}{text} = $oDoc;
}
@ -255,7 +275,7 @@ sub build
my $oSub = $$oDoc{children}[$iIndex];
my $strName = $$oSub{name};
if ($strName eq 'text' || $strName eq 'code-block')
if ($strName eq 'text')
{
$$oOut{field}{text} = $oSub;
}
@ -351,6 +371,18 @@ sub nodeGet
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
#
@ -470,6 +502,55 @@ sub nodeList
);
}
####################################################################################################################################
# nodeRemove
#
# Remove a child node.
####################################################################################################################################
sub nodeRemove
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oChildRemove
) =
logDebugParam
(
OP_DOC_NODE_REMOVE, \@_,
{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");
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# nameGet
####################################################################################################################################
@ -539,6 +620,7 @@ sub paramGet
$strOperation,
$strName,
$bRequired,
$strDefault,
$strType
) =
logDebugParam
@ -546,14 +628,20 @@ sub paramGet
OP_DOC_PARAM_GET, \@_,
{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) && $bRequired)
if (!defined($strValue))
{
confess "${strType} '${strName}' in required in node '$self->{strName}'";
if ($bRequired)
{
confess "${strType} '${strName}' in required in node '$self->{strName}'";
}
$strValue = $strDefault;
}
# Return from function and log return values if any
@ -564,6 +652,51 @@ sub paramGet
);
}
####################################################################################################################################
# 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
(
OP_DOC_PARAM_TEST, \@_,
{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
#
@ -585,7 +718,7 @@ sub paramSet
(
OP_DOC_PARAM_SET, \@_,
{name => 'strName', trace => true},
{name => 'strValue', trace => true},
{name => 'strValue', required => false, trace => true},
{name => 'strType', default => 'param', trace => true}
);
@ -604,7 +737,19 @@ sub fieldGet
{
my $self = shift;
return $self->paramGet(shift, shift, 'field');
return $self->paramGet(shift, shift, shift, 'field');
}
####################################################################################################################################
# fieldTest
#
# Test if a field exists.
####################################################################################################################################
sub fieldTest
{
my $self = shift;
return $self->paramTest(shift, shift, 'field');
}
####################################################################################################################################
@ -616,7 +761,7 @@ sub textGet
{
my $self = shift;
return $self->nodeBless($self->paramGet('text', shift, 'field'));
return $self->nodeBless($self->paramGet('text', shift, shift, 'field'));
}
####################################################################################################################################

View File

@ -235,18 +235,28 @@ sub helpDataWrite
my $self = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(OP_DOC_CONFIG_HELP_DATA_WRITE);
my
(
$strOperation,
$oManifest
) =
logDebugParam
(
OP_DOC_CONFIG_HELP_DATA_WRITE, \@_,
{name => 'oManifest'}
);
# Internal function used to format text by quoting it and splitting lines so it looks good in the module.
sub formatText
{
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($oDocRender->processText($oText)));
my @stryText = split("\n", trim($oManifest->variableReplace($oDocRender->processText($oText))));
my $strText;
my $iIndex = 0;
@ -321,9 +331,9 @@ sub helpDataWrite
(defined($$oOptionHash{&CONFIG_HELP_SECTION}) ? ' ' . &CONFIG_HELP_SECTION .
' => \'' . $$oOptionHash{&CONFIG_HELP_SECTION} . "',\n" : '') .
' ' . &CONFIG_HELP_SUMMARY . " =>\n" .
formatText($self->{oDocRender}, $$oOptionHash{&CONFIG_HELP_SUMMARY}, 16, 112) . ",\n" .
formatText($oManifest, $self->{oDocRender}, $$oOptionHash{&CONFIG_HELP_SUMMARY}, 16, 112) . ",\n" .
' ' . &CONFIG_HELP_DESCRIPTION . " =>\n" .
formatText($self->{oDocRender}, $$oOptionHash{&CONFIG_HELP_DESCRIPTION}, 16, 112) . "\n" .
formatText($oManifest, $self->{oDocRender}, $$oOptionHash{&CONFIG_HELP_DESCRIPTION}, 16, 112) . "\n" .
" }";
}
@ -346,9 +356,9 @@ sub helpDataWrite
" '${strCommand}' =>\n" .
" {\n" .
' ' . &CONFIG_HELP_SUMMARY . " =>\n" .
formatText($self->{oDocRender}, $$oCommandHash{&CONFIG_HELP_SUMMARY}, 16, 112) . ",\n" .
formatText($oManifest, $self->{oDocRender}, $$oCommandHash{&CONFIG_HELP_SUMMARY}, 16, 112) . ",\n" .
' ' . &CONFIG_HELP_DESCRIPTION . " =>\n" .
formatText($self->{oDocRender}, $$oCommandHash{&CONFIG_HELP_DESCRIPTION}, 16, 112) . ",\n" .
formatText($oManifest, $self->{oDocRender}, $$oCommandHash{&CONFIG_HELP_DESCRIPTION}, 16, 112) . ",\n" .
"\n";
# Iterate options
@ -385,9 +395,9 @@ sub helpDataWrite
" '${strOption}' =>\n" .
" {\n" .
' ' . &CONFIG_HELP_SUMMARY . " =>\n" .
formatText($self->{oDocRender}, $$oOptionHash{&CONFIG_HELP_SUMMARY}, 24, 104) . ",\n" .
formatText($oManifest, $self->{oDocRender}, $$oOptionHash{&CONFIG_HELP_SUMMARY}, 24, 104) . ",\n" .
' ' . &CONFIG_HELP_DESCRIPTION . " =>\n" .
formatText($self->{oDocRender}, $$oOptionHash{&CONFIG_HELP_DESCRIPTION}, 24, 104) . "\n" .
formatText($oManifest, $self->{oDocRender}, $$oOptionHash{&CONFIG_HELP_DESCRIPTION}, 24, 104) . "\n" .
" }";
$bExtraLinefeed = true;
@ -612,7 +622,6 @@ sub helpCommandDocGet
return $oOption, $strSection;
}
# Working variables
my $oConfigHash = $self->{oConfigHash};
my $oOperationDoc = $self->{oDoc}->nodeGet('operation');

View File

@ -0,0 +1,621 @@
####################################################################################################################################
# DOC EXECUTE MODULE
####################################################################################################################################
package BackRestDoc::Common::DocExecute;
use parent 'BackRestDoc::Common::DocRender';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use Storable qw(dclone);
use lib dirname($0) . '/../lib';
use BackRest::Common::Ini;
use BackRest::Common::Log;
use BackRest::Common::String;
use BackRest::FileCommon;
use lib dirname($0) . '/../test/lib';
use BackRestTest::Common::ExecuteTest;
use BackRestTest::Common::HostTest;
use BackRestDoc::Common::DocManifest;
####################################################################################################################################
# Operation constants
####################################################################################################################################
use constant OP_DOC_EXECUTE => 'DocExecute';
use constant OP_DOC_EXECUTE_BACKREST_CONFIG => OP_DOC_EXECUTE . '->backrestConfig';
use constant OP_DOC_EXECUTE_EXECUTE => OP_DOC_EXECUTE . '->execute';
use constant OP_DOC_EXECUTE_NEW => OP_DOC_EXECUTE . '->new';
use constant OP_DOC_EXECUTE_POSTGRES_CONFIG => OP_DOC_EXECUTE . '->postresConfig';
use constant OP_DOC_EXECUTE_SECTION_CHILD_PROCESS => OP_DOC_EXECUTE . '->sectionChildProcess';
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift; # Class name
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strType,
$oManifest,
$strRenderOutKey,
$bExe
) =
logDebugParam
(
OP_DOC_EXECUTE_NEW, \@_,
{name => 'strType'},
{name => 'oManifest'},
{name => 'strRenderOutKey'},
{name => 'bExe'}
);
# Create the class hash
my $self = $class->SUPER::new($strType, $oManifest, $strRenderOutKey);
bless $self, $class;
$self->{bExe} = $bExe;
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# execute
####################################################################################################################################
sub execute
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oSection,
$strHostName,
$oCommand,
$iIndent
) =
logDebugParam
(
OP_DOC_EXECUTE_EXECUTE, \@_,
{name => 'oSection'},
{name => 'strHostName'},
{name => 'oCommand'},
{name => 'iIndent', default => 1}
);
# Working variables
my $strCommand;
my $strOutput;
if ($oCommand->fieldTest('actual-command'))
{
$strCommand = $oCommand->fieldGet('actual-command');
$strOutput = $oCommand->fieldGet('actual-output', false);
}
else
{
# Command variables
$strCommand = trim($oCommand->fieldGet('exe-cmd'));
my $strUser = $oCommand->paramGet('user', false, 'postgres');
my $bExeOutput = $oCommand->paramTest('output', 'y');
my $strVariableKey = $oCommand->paramGet('variable-key', false);
my $iExeExpectedError = $oCommand->paramGet('err-expect', false);
$strCommand = $self->{oManifest}->variableReplace(
(defined($strUser) && $strUser eq 'vagrant' ? '' :
('sudo ' . ($strUser eq 'root' ? '' : "-u ${strUser} "))) . $strCommand);
# Add continuation chars and proper spacing
$strCommand =~ s/[ ]*\n[ ]*/ \\\n /smg;
if (!$oCommand->paramTest('show', 'n') && $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)) > 80)
{
confess &log(ERROR, "command has a line > 80 characters:\n${strCommand}");
}
}
}
&log(DEBUG, (' ' x $iIndent) . "execute: $strCommand");
if (!$oCommand->paramTest('skip', 'y'))
{
if ($self->{bExe} && $self->isRequired($oSection))
{
# 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,
{iExpectedExitStatus => $iExeExpectedError,
bSuppressError => $oCommand->paramTest('err-suppress', 'y'),
iRetrySeconds => $oCommand->paramGet('retry', false)});
$oExec->begin();
$oExec->end();
if ($bExeOutput && defined($oExec->{strOutLog}) && $oExec->{strOutLog} ne '')
{
$strOutput = $oExec->{strOutLog};
# Trim off extra linefeeds before and after
$strOutput =~ s/^\n+|\n$//g;
if ($strCommand =~ / pg\_backrest /)
{
$strOutput =~ s/^ //smg;
$strOutput =~ s/^[0-9]{4}-[0-1][0-9]-[0-3][0-9] [0-2][0-9]:[0-6][0-9]:[0-6][0-9]\.[0-9]{3} T[0-9]{2} //smg;
}
}
if (defined($iExeExpectedError))
{
$strOutput .= trim($oExec->{strErrorLog});
}
# Output is assigned to a var
if (defined($strVariableKey))
{
$self->{oManifest}->variableSet($strVariableKey, trim($oExec->{strOutLog}));
}
elsif (!$oCommand->paramTest('filter', 'n') && $bExeOutput && defined($strOutput))
{
my $strHighLight = $self->{oManifest}->variableReplace($oCommand->fieldGet('exe-highlight', false));
if (!defined($strHighLight))
{
confess &log(ERROR, 'filter requires highlight definition: ' . $strCommand);
}
my $iFilterContext = $oCommand->paramGet('filter-context', false, 2);
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[@stryOutput - 1];
}
}
}
}
elsif ($bExeOutput)
{
$strOutput = 'Output suppressed for testing';
}
}
if (defined($strVariableKey) && !defined($self->{oManifest}->variableGet($strVariableKey)))
{
$self->{oManifest}->variableSet($strVariableKey, '[Test Variable]');
}
$oCommand->fieldSet('actual-command', $strCommand);
$oCommand->fieldSet('actual-output', $strOutput);
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strCommand', value => $strCommand, trace => true},
{name => 'strOutput', value => $strOutput, trace => true}
);
}
####################################################################################################################################
# backrestConfig
####################################################################################################################################
sub backrestConfig
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oSection,
$oConfig,
$iDepth
) =
logDebugParam
(
OP_DOC_EXECUTE_BACKREST_CONFIG, \@_,
{name => 'oSection'},
{name => 'oConfig'},
{name => 'iDepth'}
);
# Working variables
my $strFile;
my $strConfig;
if ($oConfig->fieldTest('actual-file'))
{
$strFile = $oConfig->fieldGet('actual-file');
$strConfig = $oConfig->fieldGet('actual-config');
}
else
{
# Get filename
$strFile = $self->{oManifest}->variableReplace($oConfig->paramGet('file'));
&log(DEBUG, (' ' x $iDepth) . 'process backrest config: ' . $strFile);
if ($self->{bExe} && $self->isRequired($oSection))
{
# 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}{$strFile})
}
foreach my $oOption ($oConfig->nodeList('backrest-config-option'))
{
my $strSection = $oOption->paramGet('section');
my $strKey = $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}{$strFile}{$strSection}{$strKey});
if (keys(${$self->{config}}{$strHostName}{$strFile}{$strSection}) == 0)
{
delete(${$self->{config}}{$strHostName}{$strFile}{$strSection});
}
&log(DEBUG, (' ' x ($iDepth + 1)) . "reset ${strSection}->${strKey}");
}
else
{
${$self->{config}}{$strHostName}{$strFile}{$strSection}{$strKey} = $strValue;
&log(DEBUG, (' ' x ($iDepth + 1)) . "set ${strSection}->${strKey} = ${strValue}");
}
}
my $strLocalFile = "/home/vagrant/data/db-master/etc/pg_backrest.conf";
# Save the ini file
iniSave($strLocalFile, $self->{config}{$strHostName}{$strFile}, true);
$strConfig = fileStringRead($strLocalFile);
$oHost->copyTo($strLocalFile, $strFile, $oConfig->paramGet('owner', false, 'postgres:postgres'), '640');
}
else
{
$strConfig = 'Config suppressed for testing';
}
$oConfig->fieldSet('actual-file', $strFile);
$oConfig->fieldSet('actual-config', $strConfig);
}
# 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
(
OP_DOC_EXECUTE_POSTGRES_CONFIG, \@_,
{name => 'oSection'},
{name => 'oConfig'},
{name => 'iDepth'}
);
# Working variables
my $strFile;
my $strConfig;
if ($oConfig->fieldTest('actual-file'))
{
$strFile = $oConfig->fieldGet('actual-file');
$strConfig = $oConfig->fieldGet('actual-config');
}
else
{
# Get filename
$strFile = $self->{oManifest}->variableReplace($oConfig->paramGet('file'));
if ($self->{bExe} && $self->isRequired($oSection))
{
# 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/vagrant/data/db-master/etc/postgresql.conf';
$oHost->copyFrom($strFile, $strLocalFile);
if (!defined(${$self->{'pg-config'}}{$strHostName}{$strFile}{base}) && $self->{bExe})
{
${$self->{'pg-config'}}{$strHostName}{$strFile}{base} = fileStringRead($strLocalFile);
}
my $oConfigHash = $self->{'pg-config'}{$strHostName}{$strFile};
my $oConfigHashNew;
if (!defined($$oConfigHash{old}))
{
$oConfigHashNew = {};
$$oConfigHash{old} = {}
}
else
{
$oConfigHashNew = dclone($$oConfigHash{old});
}
&log(DEBUG, (' ' x $iDepth) . 'process postgres config: ' . $strFile);
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})
{
fileStringWrite($strLocalFile, $$oConfigHash{base} .
(defined($strConfig) ? "\n# pgBackRest Configuration\n${strConfig}\n" : ''));
$oHost->copyTo($strLocalFile, $strFile, 'postgres:postgres', '640');
}
$$oConfigHash{old} = $oConfigHashNew;
}
else
{
$strConfig = 'Config suppressed for testing';
}
$oConfig->fieldSet('actual-file', $strFile);
$oConfig->fieldSet('actual-config', $strConfig);
}
# 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}
);
}
####################################################################################################################################
# sectionChildProcesss
####################################################################################################################################
sub sectionChildProcess
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oSection,
$oChild,
$iDepth
) =
logDebugParam
(
OP_DOC_EXECUTE_SECTION_CHILD_PROCESS, \@_,
{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) && !$oChild->paramTest('created', true))
{
my $strName = $self->{oManifest}->variableReplace($oChild->paramGet('name'));
my $strUser = $self->{oManifest}->variableReplace($oChild->paramGet('user'));
my $strImage = $self->{oManifest}->variableReplace($oChild->paramGet('image'));
my $strOS = $self->{oManifest}->variableReplace($oChild->paramGet('os', false));
my $strMount = $self->{oManifest}->variableReplace($oChild->paramGet('mount', false));
if (defined($self->{host}{$strName}))
{
confess &log(ERROR, 'cannot add host ${strName} because the host already exists');
}
my $oHost = new BackRestTest::Common::HostTest($strName, $strImage, $strUser, $strOS, $strMount);
$self->{host}{$strName} = $oHost;
$self->{oManifest}->variableSet("host-${strName}-ip", $oHost->{strIP});
# Execute cleanup commands
foreach my $oExecute ($oChild->nodeList('execute'))
{
$self->execute($oSection, $strName, $oExecute, $iDepth + 1);
}
$oHost->executeSimple("sh -c 'echo \"\" >> /etc/hosts\'");
$oHost->executeSimple("sh -c 'echo \"# Test Hosts\" >> /etc/hosts'");
# Add all other host IPs to this host
foreach my $strOtherHostName (sort(keys($self->{host})))
{
if ($strOtherHostName ne $strName)
{
my $oOtherHost = $self->{host}{$strOtherHostName};
$oHost->executeSimple("sh -c 'echo \"$oOtherHost->{strIP} ${strOtherHostName}\" >> /etc/hosts'");
}
}
# Add this host IP to all other hosts
foreach my $strOtherHostName (sort(keys($self->{host})))
{
if ($strOtherHostName ne $strName)
{
my $oOtherHost = $self->{host}{$strOtherHostName};
$oOtherHost->executeSimple("sh -c 'echo \"$oHost->{strIP} ${strName}\" >> /etc/hosts'");
}
}
$oChild->paramSet('created', true);
}
}
# 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;

View File

@ -0,0 +1,536 @@
####################################################################################################################################
# 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 Scalar::Util qw(blessed);
use lib dirname($0) . '/../lib';
use BackRest::Common::Log;
use BackRest::Common::String;
use BackRest::FileCommon;
####################################################################################################################################
# Operation constants
####################################################################################################################################
use constant OP_DOC_MANIFEST => 'DocManifest';
use constant OP_DOC_MANIFEST_NEW => OP_DOC_MANIFEST . '->new';
use constant OP_DOC_MANIFEST_RENDER_GET => OP_DOC_MANIFEST . '->renderGet';
use constant OP_DOC_MANIFEST_RENDER_LIST => OP_DOC_MANIFEST . '->renderList';
use constant OP_DOC_MANIFEST_RENDER_OUT_GET => OP_DOC_MANIFEST . '->renderOutGet';
use constant OP_DOC_MANIFEST_RENDER_OUT_LIST => OP_DOC_MANIFEST . '->renderOutList';
use constant OP_DOC_MANIFEST_SOURCE_GET => OP_DOC_MANIFEST . '->sourceGet';
use constant OP_DOC_MANIFEST_VARIABLE_LIST_PARSE => OP_DOC_MANIFEST . '->variableListParse';
####################################################################################################################################
# File constants
####################################################################################################################################
use constant FILE_MANIFEST => 'manifest.xml';
####################################################################################################################################
# Render constants
####################################################################################################################################
use constant RENDER => 'render';
use constant RENDER_FILE => 'file';
use constant RENDER_TYPE => 'type';
use constant RENDER_TYPE_HTML => 'html';
push @EXPORT, qw(RENDER_TYPE_HTML);
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->{stryKeyword},
$self->{stryRequire},
my $oVariableOverride,
$self->{strDocPath}
) =
logDebugParam
(
OP_DOC_MANIFEST_NEW, \@_,
{name => 'stryKeyword'},
{name => 'stryRequire'},
{name => 'oVariableOverride', required => false},
{name => 'strDocPath', required => false},
);
# Set the base path if it was not passed in
if (!defined($self->{strDocPath}))
{
$self->{strDocPath} = abs_path(dirname($0));
}
# 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');
logDebugMisc
(
$strOperation, 'load source',
{name => 'strKey', value => $strKey}
);
$$oSourceHash{doc} = new BackRestDoc::Common::Doc("$self->{strDocPath}/xml/${strKey}.xml");
# Read variables from source
$self->variableListParse($$oSourceHash{doc}->nodeGet('variable-list', false), $oVariableOverride);
${$self->{oManifest}}{source}{$strKey} = $oSourceHash;
}
# 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);
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);
$$oRenderOutHash{source} = $strSource;
# Get the filename if this is a pdf
$$oRenderOutHash{menu} = $oRenderOut->paramGet('menu', false);
if (defined($$oRenderOutHash{menu}) && $strType ne RENDER_TYPE_HTML)
{
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;
}
# Read variables from manifest
$self->variableListParse($self->{oManifestXml}->nodeGet('variable-list', false), $oVariableOverride);
# use Data::Dumper; confess Dumper($self->{oVariable});
# 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', 'pg_backrest'));
}
####################################################################################################################################
# keywordMatch
#
# See if all the keywords were on the command line.
####################################################################################################################################
sub keywordMatch
{
my $self = shift;
my $strKeywordRequired = shift;
if (defined($strKeywordRequired))
{
for my $strKeyword (split(',', $strKeywordRequired))
{
$strKeyword = trim($strKeyword);
if (!grep(/^$strKeyword$/, @{$self->{stryKeyword}}))
{
return false;
}
}
}
return true;
}
####################################################################################################################################
# variableListParse
#
# Parse a variable list and store variables.
####################################################################################################################################
sub variableListParse
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oVariableList,
$oVariableOverride
) =
logDebugParam
(
OP_DOC_MANIFEST_VARIABLE_LIST_PARSE, \@_,
{name => '$oVariableList', required => false},
{name => '$oVariableOverride', required => false}
);
if (defined($oVariableList))
{
foreach my $oVariable ($oVariableList->nodeList('variable'))
{
if ($self->keywordMatch($oVariable->paramGet('keyword', false)))
{
my $strKey = $oVariable->paramGet('key');
my $strValue = $oVariable->valueGet();
if ($oVariable->paramTest('eval', 'y'))
{
$strValue = eval $strValue;
if ($@)
{
confess &log(ERROR, "unable to evaluate ${strKey}: $@\n" . $oVariable->valueGet());
}
}
$self->variableSet($strKey, defined($$oVariableOverride{$strKey}) ? $$oVariableOverride{$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 undef;
}
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;
}
####################################################################################################################################
# sourceGet
####################################################################################################################################
sub sourceGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strSource
) =
logDebugParam
(
OP_DOC_MANIFEST_SOURCE_GET, \@_,
{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(OP_DOC_MANIFEST_RENDER_LIST);
# 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
(
OP_DOC_MANIFEST_RENDER_GET, \@_,
{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
(
OP_DOC_MANIFEST_RENDER_OUT_LIST, \@_,
{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
) =
logDebugParam
(
OP_DOC_MANIFEST_RENDER_OUT_GET, \@_,
{name => 'strType', trace => true},
{name => 'strKey', trace => true}
);
# use Data::Dumper; print Dumper(${$self->{oManifest}}{render});
if (!defined(${$self->{oManifest}}{render}{$strType}{out}{$strKey}))
{
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}}
);
}
1;

View File

@ -15,6 +15,8 @@ use lib dirname($0) . '/../lib';
use BackRest::Common::Log;
use BackRest::Common::String;
use BackRestDoc::Common::DocManifest;
####################################################################################################################################
# Operation constants
####################################################################################################################################
@ -26,8 +28,6 @@ use constant OP_DOC_RENDER_PROCESS_TEXT => OP_DOC_RE
use constant OP_DOC_RENDER_NEW => OP_DOC_RENDER . '->new';
use constant OP_DOC_RENDER_SAVE => OP_DOC_RENDER . '->save';
# use HTML::HTML5::Builder qw[:standard JQUERY];
####################################################################################################################################
# Render tags for various output types
####################################################################################################################################
@ -35,9 +35,10 @@ my $oRenderTag =
{
'markdown' =>
{
'quote' => ['"', '"'],
'b' => ['**', '**'],
'i' => ['_', '_'],
'bi' => ['_**', '**_'],
# 'bi' => ['_**', '**_'],
'ul' => ["\n", "\n"],
'ol' => ["\n", "\n"],
'li' => ['- ', "\n"],
@ -48,17 +49,18 @@ my $oRenderTag =
'param' => ['`', '`'],
'setting' => ['`', '`'],
'code' => ['`', '`'],
'code-block' => ['```', '```'],
'exe' => [undef, ''],
# 'code-block' => ['```', '```'],
# 'exe' => [undef, ''],
'backrest' => [undef, ''],
'postgres' => ['PostgreSQL', '']
},
'text' =>
{
'quote' => ['"', '"'],
'b' => ['', ''],
'i' => ['', ''],
'bi' => ['', ''],
# 'bi' => ['', ''],
'ul' => ["\n", "\n"],
'ol' => ["\n", "\n"],
'li' => ['* ', "\n"],
@ -76,15 +78,47 @@ my $oRenderTag =
'postgres' => ['PostgreSQL', '']
},
'latex' =>
{
'quote' => ['``', '"'],
'b' => ['\textbf{', '}'],
'i' => ['\textit{', '}'],
# 'bi' => ['', ''],
# 'ul' => ["\n", "\n"],
# 'ol' => ["\n", "\n"],
# 'li' => ['* ', "\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, ''],
'postgres' => ['PostgreSQL', '']
},
'html' =>
{
'quote' => ['<q>', '</q>'],
'b' => ['<b>', '</b>'],
'i' => ['<i>', '</i>'],
'bi' => ['<i><b>', '</b></i>'],
# 'bi' => ['<i><b>', '</b></i>'],
'ul' => ['<ul>', '</ul>'],
'ol' => ['<ol>', '</ol>'],
'li' => ['<li>', '</li>'],
'id' => ['<span class="id">', '</span>'],
'host' => ['<span class="host">', '</span>'],
'file' => ['<span class="file">', '</span>'],
'path' => ['<span class="path">', '</span>'],
'cmd' => ['<span class="cmd">', '</span>'],
@ -93,9 +127,9 @@ my $oRenderTag =
'br-setting' => ['<span class="br-setting">', '</span>'],
'pg-option' => ['<span class="pg-option">', '</span>'],
'pg-setting' => ['<span class="pg-setting">', '</span>'],
'code' => ['<id>', '</id>'],
'code' => ['<span class="id">', '</span>'],
'code-block' => ['<code-block>', '</code-block>'],
'exe' => ['<id>', '</id>'],
'exe' => [undef, ''],
'setting' => ['<span class="br-setting">', '</span>'], # !!! This will need to be fixed
'backrest' => [undef, ''],
'postgres' => ['<span class="postgres">PostgreSQL</span>', '']
@ -117,22 +151,76 @@ sub new
(
my $strOperation,
$self->{strType},
$self->{strProjectName},
$self->{strExeName}
$self->{oManifest},
$self->{strRenderOutKey},
) =
logDebugParam
(
OP_DOC_RENDER_NEW, \@_,
{name => 'strType'},
{name => 'strProjectName'},
{name => 'strExeName'}
{name => 'oManifest'},
{name => 'strRenderOutKey', required => false}
);
$$oRenderTag{markdown}{backrest}[0] = $self->{strProjectName};
$$oRenderTag{markdown}{exe}[0] = $self->{strExeName};
$$oRenderTag{text}{backrest}[0] = $self->{strProjectName};
$$oRenderTag{text}{exe}[0] = $self->{strExeName};
$$oRenderTag{html}{backrest}[0] = "<span class=\"backrest\">$self->{strProjectName}</span>";
# 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] = "<span class=\"backrest\">{[project]}</span>";
$$oRenderTag{html}{exe}[0] = "<span class=\"file\">{[project-exe]}</span>";
if (defined($self->{strRenderOutKey}))
{
# Copy page data to self
my $oRenderOut = $self->{oManifest}->renderOutGet($self->{strType} eq 'latex' ? 'pdf' : $self->{strType}, $self->{strRenderOutKey});
# Get the reference if this is the backrest project
if ($self->{oManifest}->isBackRest())
{
$self->{oReference} = new BackRestDoc::Common::DocConfig(${$self->{oManifest}->sourceGet('reference')}{doc}, $self);
}
if (defined($$oRenderOut{source}) && $$oRenderOut{source} eq 'reference')
{
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}");
}
}
else
{
$self->{oDoc} = ${$self->{oManifest}->sourceGet($self->{strRenderOutKey})}{doc};
}
}
if (defined($self->{strRenderOutKey}))
{
# Build the doc
$self->build($self->{oDoc});
# Get required sections
foreach my $strPath (@{$self->{oManifest}->{stryRequire}})
{
if (defined(${$self->{oSection}}{$strPath}))
{
$self->required($strPath);
}
}
}
# Return from function and log return values if any
return logDebugReturn
@ -142,6 +230,183 @@ sub new
);
}
####################################################################################################################################
# variableReplace
#
# Replace variables in the string.
####################################################################################################################################
sub variableReplace
{
my $self = shift;
return $self->{oManifest}->variableReplace(shift, $self->{strType});
}
####################################################################################################################################
# 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);
}
####################################################################################################################################
# build
#
# Build the section map and perform keyword matching.
####################################################################################################################################
sub build
{
my $self = shift;
my $oNode = shift;
my $oParent = shift;
my $strPath = shift;
# &log(INFO, " node " . $oNode->nameGet());
my $strName = $oNode->nameGet();
if (defined($oParent))
{
if (!$self->{oManifest}->keywordMatch($oNode->paramGet('keyword', false)))
{
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);
}
}
else
{
&log(DEBUG, ' build document');
$self->{oSection} = {};
}
if ($strName eq '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);
}
# Iterate all nodes
foreach my $oChild ($oNode->nodeList(undef, false))
{
$self->build($oChild, $oNode, $strPath);
}
}
####################################################################################################################################
# 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\/.*$/)
{
&log(INFO, " 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;
}
####################################################################################################################################
# process
#
@ -168,7 +433,6 @@ sub process
);
my $strType = $self->{strType};
my $strProjectName = $self->{strProjectName};
my $strBuffer = "";
my $bList = $oDoc->nameGet() =~ /.*-bullet-list$/;
@ -211,7 +475,7 @@ sub process
$strBuffer .= 'v' . $oDoc->paramGet('version') . ': ';
}
$strBuffer .= ($iDepth == 1 ? "${strProjectName}<br/>" : '') . $strTitle;
$strBuffer .= ($iDepth == 1 ? "{[project]}<br/>" : '') . $strTitle;
if (defined($oDoc->paramGet('date', false)))
{
@ -369,6 +633,10 @@ sub processTag
$strBuffer = '<a href="' . $strUrl . '">' . $oTag->valueGet() . '</a>';
}
elsif ($strType eq 'latex')
{
$strBuffer = $oTag->valueGet();
}
else
{
confess "tag link not valid for type ${strType}";
@ -439,6 +707,11 @@ sub processText
{
if (ref(\$oNode) eq "SCALAR")
{
if ($oNode =~ /\"/)
{
confess &log(ERROR, "unable to process quotes in string (use <quote> instead):\n${oNode}");
}
$strBuffer .= $oNode;
}
else
@ -462,6 +735,17 @@ sub processText
$strBuffer =~ s/^ //smg;
# }
if ($strType eq 'latex')
{
$strBuffer =~ s/\&mdash\;/---/g;
$strBuffer =~ s/\&lt\;/\</g;
$strBuffer =~ s/\<\=/\$\\leq\$/g;
$strBuffer =~ s/\>\=/\$\\geq\$/g;
# $strBuffer =~ s/\_/\\_/g;
}
$strBuffer = $self->variableReplace($strBuffer);
# Return from function and log return values if any
return logDebugReturn
(

View File

@ -44,13 +44,15 @@ sub new
(
my $strOperation,
$self->{strName},
$self->{strTitle}
$self->{strTitle},
$self->{bPretty}
) =
logDebugParam
(
OP_DOC_HTML_BUILDER_NEW, \@_,
{name => 'strName'},
{name => 'strTitle'}
{name => 'strTitle'},
{name => 'bPretty', default => false}
);
$self->{oBody} = new BackRestDoc::Html::DocHtmlElement(HTML_BODY);
@ -73,7 +75,7 @@ sub indent
my $self = shift;
my $iDepth = shift;
return (' ' x $iDepth);
return $self->{bPretty} ? (' ' x $iDepth) : '';
}
####################################################################################################################################
@ -85,7 +87,7 @@ sub lf
{
my $self = shift;
return "\n";
return $self->{bPretty} ? "\n" : '';
}
####################################################################################################################################
@ -118,8 +120,8 @@ sub htmlRender
logDebugParam
(
OP_DOC_HTML_BUILDER_HTML_RENDER, \@_,
{name => 'oElement'},
{name => 'iDepth'}
{name => 'oElement', trace => true},
{name => 'iDepth', trace => true}
);
# Build the header
@ -131,8 +133,19 @@ sub htmlRender
if (defined($oElement->{strContent}))
{
$oElement->{strContent} =~ s/\n/\<br\/>\n/g;
$strHtml .= $self->lf() . trim($oElement->{strContent}) . $self->lf() . $self->indent($iDepth);
if (!defined($oElement->{bPre}) || !$oElement->{bPre})
{
$oElement->{strContent} =~ s/\n/\<br\/>\n/g;
$oElement->{strContent} = trim($oElement->{strContent});
$strHtml .= $self->lf();
}
$strHtml .= $oElement->{strContent};
if (!defined($oElement->{bPre}) || !$oElement->{bPre})
{
$strHtml .= $self->lf() . $self->indent($iDepth);
}
}
else
{

View File

@ -32,6 +32,8 @@ use constant HTML_A => 'a';
push @EXPORT, qw(HTML_A);
use constant HTML_BODY => 'body';
push @EXPORT, qw(HTML_BODY);
use constant HTML_PRE => 'pre';
push @EXPORT, qw(HTML_PRE);
use constant HTML_DIV => 'div';
push @EXPORT, qw(HTML_DIV);
use constant HTML_SPAN => 'span';
@ -69,6 +71,7 @@ sub new
$self->{strContent} = $$oParam{strContent};
$self->{strId} = $$oParam{strId};
$self->{strRef} = $$oParam{strRef};
$self->{bPre} = $$oParam{bPre};
# Return from function and log return values if any
return logDebugReturn

View File

@ -2,6 +2,7 @@
# DOC HTML PAGE MODULE
####################################################################################################################################
package BackRestDoc::Html::DocHtmlPage;
use parent 'BackRestDoc::Common::DocExecute';
use strict;
use warnings FATAL => qw(all);
@ -15,15 +16,11 @@ use File::Copy;
use Storable qw(dclone);
use lib dirname($0) . '/../lib';
use BackRest::Common::Ini;
use BackRest::Common::Log;
use BackRest::Common::String;
use BackRest::Config::ConfigHelp;
use BackRest::FileCommon;
use lib dirname($0) . '/../test/lib';
use BackRestTest::Common::ExecuteTest;
use BackRestDoc::Common::DocManifest;
use BackRestDoc::Html::DocHtmlBuilder;
use BackRestDoc::Html::DocHtmlElement;
@ -33,7 +30,6 @@ use BackRestDoc::Html::DocHtmlElement;
use constant OP_DOC_HTML_PAGE => 'DocHtmlPage';
use constant OP_DOC_HTML_PAGE_BACKREST_CONFIG_PROCESS => OP_DOC_HTML_PAGE . '->backrestConfigProcess';
use constant OP_DOC_HTML_PAGE_EXECUTE => OP_DOC_HTML_PAGE . '->execute';
use constant OP_DOC_HTML_PAGE_NEW => OP_DOC_HTML_PAGE . '->new';
use constant OP_DOC_HTML_PAGE_POSTGRES_CONFIG_PROCESS => OP_DOC_HTML_PAGE . '->postgresConfigProcess';
use constant OP_DOC_HTML_PAGE_PROCESS => OP_DOC_HTML_PAGE . '->process';
@ -46,35 +42,25 @@ 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
(
my $strOperation,
$self->{oSite},
$self->{strPageId},
$self->{bExe}
$strOperation,
$oManifest,
$strRenderOutKey,
$bExe
) =
logDebugParam
(
OP_DOC_HTML_PAGE_NEW, \@_,
{name => 'oSite'},
{name => 'strPageId'},
{name => 'bExe', default => true}
{name => 'oManifest'},
{name => 'strRenderOutKey'},
{name => 'bExe'}
);
#
# use Data::Dumper;
# confess Dumper(${$self->{oSite}->{oSite}}{common}{oRender});
# Copy page data to self
$self->{oPage} = ${$self->{oSite}->{oSite}}{page}{$self->{strPageId}};
$self->{oDoc} = ${$self->{oPage}}{'oDoc'};
$self->{oRender} = ${$self->{oSite}->{oSite}}{common}{oRender};
$self->{oReference} = ${$self->{oSite}->{oSite}}{common}{oReference};
# Create the class hash
my $self = $class->SUPER::new(RENDER_TYPE_HTML, $oManifest, $strRenderOutKey, $bExe);
bless $self, $class;
# Return from function and log return values if any
return logDebugReturn
@ -84,100 +70,6 @@ sub new
);
}
####################################################################################################################################
# execute
####################################################################################################################################
sub execute
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oCommand,
$iIndent
) =
logDebugParam
(
OP_DOC_HTML_PAGE_EXECUTE, \@_,
{name => 'oCommand'},
{name => 'iIndent', default => 1}
);
# Working variables
my $strOutput;
# Command variables
my $strCommand = trim($oCommand->fieldGet('exe-cmd'));
my $strUser = $oCommand->fieldGet('exe-user', false);
my $bSuppressError = defined($oCommand->fieldGet('exe-err-suppress', false)) ? $oCommand->fieldGet('exe-err-suppress') : false;
my $bSuppressStdErr = defined($oCommand->fieldGet('exe-err-suppress-stderr', false)) ?
$oCommand->fieldGet('exe-err-suppress-stderr') : false;
my $bExeSkip = defined($oCommand->fieldGet('exe-skip', false)) ? $oCommand->fieldGet('exe-skip') : false;
my $bExeOutput = defined($oCommand->fieldGet('exe-output', false)) ? $oCommand->fieldGet('exe-output') : false;
my $bExeRetry = defined($oCommand->fieldGet('exe-retry', false)) ? $oCommand->fieldGet('exe-retry') : false;
my $strExeVar = defined($oCommand->fieldGet('exe-var', false)) ? $oCommand->fieldGet('exe-var') : undef;
my $iExeExpectedError = defined($oCommand->fieldGet('exe-err-expect', false)) ? $oCommand->fieldGet('exe-err-expect') : undef;
if ($bExeRetry)
{
sleep(1);
}
$strUser = defined($strUser) ? $strUser : 'postgres';
$strCommand = $self->{oSite}->variableReplace(
($strUser eq 'vagrant' ? '' : 'sudo ' . ($strUser eq 'root' ? '' : "-u ${strUser} ")) . $strCommand);
&log(INFO, (' ' x $iIndent) . "execute: $strCommand");
if (!$bExeSkip)
{
if ($self->{bExe})
{
my $oExec = new BackRestTest::Common::ExecuteTest($strCommand,
{bSuppressError => $bSuppressError,
bSuppressStdErr => $bSuppressStdErr,
iExpectedExitStatus => $iExeExpectedError});
$oExec->begin();
$oExec->end();
if ($bExeOutput && defined($oExec->{strOutLog}) && $oExec->{strOutLog} ne '')
{
$strOutput = trim($oExec->{strOutLog});
$strOutput =~ s/^[0-9]{4}-[0-1][0-9]-[0-3][0-9] [0-2][0-9]:[0-6][0-9]:[0-6][0-9]\.[0-9]{3} T[0-9]{2} //smg;
}
if (defined($strExeVar))
{
$self->{oSite}->variableSet($strExeVar, trim($oExec->{strOutLog}));
}
if (defined($iExeExpectedError))
{
$strOutput .= trim($oExec->{strErrorLog});
}
}
elsif ($bExeOutput)
{
$strOutput = 'Output suppressed for testing';
}
}
if (defined($strExeVar) && !defined($self->{oSite}->variableGet($strExeVar)))
{
$self->{oSite}->variableSet($strExeVar, '[Unset Variable]');
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => '$strCommand', value => $strCommand, trace => true},
{name => '$strOutput', value => $strOutput, trace => true}
);
}
####################################################################################################################################
# process
#
@ -194,23 +86,13 @@ sub process
my $oPage = $self->{oDoc};
# Initialize page
my $strTitle = ${$self->{oRender}}{strProjectName} .
my $strTitle = "{[project]}" .
(defined($oPage->paramGet('title', false)) ? ' ' . $oPage->paramGet('title') : '');
my $strSubTitle = $oPage->paramGet('subtitle', false);
my $oHtmlBuilder = new BackRestDoc::Html::DocHtmlBuilder("${$self->{oRender}}{strProjectName} - Reliable PostgreSQL Backup",
$strTitle . (defined($strSubTitle) ? " - ${strSubTitle}" : ''));
# Execute cleanup commands
if (defined($self->{oDoc}->nodeGet('cleanup', false)))
{
&log(INFO, "do cleanup");
foreach my $oExecute ($oPage->nodeGet('cleanup')->nodeList('execute'))
{
$self->execute($oExecute);
}
}
my $oHtmlBuilder = new BackRestDoc::Html::DocHtmlBuilder("{[project]} - Reliable PostgreSQL Backup",
$strTitle . (defined($strSubTitle) ? " - ${strSubTitle}" : ''),
$self->{bPretty});
# Generate header
my $oPageHeader = $oHtmlBuilder->bodyGet()->addNew(HTML_DIV, 'page-header');
@ -229,24 +111,24 @@ sub process
# Generate menu
my $oMenuBody = $oHtmlBuilder->bodyGet()->addNew(HTML_DIV, 'page-menu')->addNew(HTML_DIV, 'menu-body');
if ($self->{strPageId} ne 'index')
if ($self->{strRenderOutKey} ne 'index')
{
my $oPage = ${$self->{oSite}->{oSite}}{page}{'index'};
my $oRenderOut = $self->{oManifest}->renderOutGet(RENDER_TYPE_HTML, 'index');
$oMenuBody->
addNew(HTML_DIV, 'menu')->
addNew(HTML_A, 'menu-link', {strContent => $$oPage{strMenuTitle}, strRef => '{[backrest-url-root]}'});
addNew(HTML_A, 'menu-link', {strContent => $$oRenderOut{menu}, strRef => '{[project-url-root]}'});
}
foreach my $strPageId(sort(keys(${$self->{oSite}->{oSite}}{page})))
foreach my $strRenderOutKey ($self->{oManifest}->renderOutList(RENDER_TYPE_HTML))
{
if ($strPageId ne $self->{strPageId} && $strPageId ne 'index')
if ($strRenderOutKey ne $self->{strRenderOutKey} && $strRenderOutKey ne 'index')
{
my $oPage = ${$self->{oSite}->{oSite}}{page}{$strPageId};
my $oRenderOut = $self->{oManifest}->renderOutGet(RENDER_TYPE_HTML, $strRenderOutKey);
$oMenuBody->
addNew(HTML_DIV, 'menu')->
addNew(HTML_A, 'menu-link', {strContent => $$oPage{strMenuTitle}, strRef => "${strPageId}.html"});
addNew(HTML_A, 'menu-link', {strContent => $$oRenderOut{menu}, strRef => "${strRenderOutKey}.html"});
}
}
@ -284,7 +166,7 @@ sub process
my $oPageFooter = $oHtmlBuilder->bodyGet()->
addNew(HTML_DIV, 'page-footer',
{strContent => ${$self->{oSite}->{oSite}}{common}{strFooter}});
{strContent => '{[html-footer]}'});
# Return from function and log return values if any
return logDebugReturn
@ -317,7 +199,7 @@ sub sectionProcess
{name => 'iDepth'}
);
&log(INFO, (' ' x ($iDepth - 1)) . 'process section: ' . $oSection->paramGet('id'));
&log($iDepth == 1 ? INFO : DEBUG, (' ' x ($iDepth + 1)) . 'process section: ' . $oSection->paramGet('id'));
if ($iDepth > 3)
{
@ -326,7 +208,6 @@ sub sectionProcess
# Working variables
$strAnchor = (defined($strAnchor) ? "${strAnchor}-" : '') . $oSection->paramGet('id');
my $oRender = $self->{oRender};
# Create the section toc element
my $oSectionTocElement = new BackRestDoc::Html::DocHtmlElement(HTML_DIV, "section${iDepth}-toc");
@ -338,7 +219,7 @@ sub sectionProcess
$oSectionElement->addNew(HTML_A, undef, {strId => $strAnchor});
# Add the section title to section and toc
my $strSectionTitle = $oRender->processText($oSection->nodeGet('title')->textGet());
my $strSectionTitle = $self->processText($oSection->nodeGet('title')->textGet());
$oSectionElement->
addNew(HTML_DIV, "section${iDepth}-title",
@ -356,7 +237,7 @@ sub sectionProcess
{
$oSectionElement->
addNew(HTML_DIV, "section-intro",
{strContent => $oRender->processText($oSection->textGet())});
{strContent => $self->processText($oSection->textGet())});
}
# Add the section body
@ -367,39 +248,51 @@ sub sectionProcess
foreach my $oChild ($oSection->nodeList())
{
&log(INFO, (' ' x $iDepth) . 'process child ' . $oChild->nameGet());
&log(DEBUG, (' ' x ($iDepth + 2)) . 'process child ' . $oChild->nameGet());
# Execute a command
if ($oChild->nameGet() eq 'execute-list')
{
my $oSectionBodyExecute = $oSectionBodyElement->addNew(HTML_DIV, "execute");
my $bFirst = true;
my $strHostName = $self->{oManifest}->variableReplace($oChild->paramGet('host'));
$oSectionBodyExecute->
addNew(HTML_DIV, "execute-title",
{strContent => $oRender->processText($oChild->nodeGet('title')->textGet()) . ':'});
{strContent => "<span class=\"host\">${strHostName}</span> <b>&#x21d2;</b> " .
$self->processText($oChild->nodeGet('title')->textGet())});
my $oExecuteBodyElement = $oSectionBodyExecute->addNew(HTML_DIV, "execute-body");
foreach my $oExecute ($oChild->nodeList('execute'))
{
my $bExeShow = defined($oExecute->fieldGet('exe-no-show', false)) ? false : true;
my $bExeExpectedError = defined($oExecute->fieldGet('exe-err-expect', false)) ? true : false;
my ($strCommand, $strOutput) = $self->execute($oExecute, $iDepth + 1);
my $bExeShow = !$oExecute->paramTest('show', 'n');
my $bExeExpectedError = defined($oExecute->paramGet('err-expect', false));
my ($strCommand, $strOutput) = $self->execute($oSection, $strHostName, $oExecute, $iDepth + 3);
if ($bExeShow)
{
# Add continuation chars and proper spacing
$strCommand =~ s/\n/\n /smg;
$oExecuteBodyElement->
addNew(HTML_DIV, "execute-body-cmd" . ($bFirst ? '-first' : ''),
{strContent => $strCommand});
addNew(HTML_PRE, "execute-body-cmd",
{strContent => $strCommand, bPre => true});
my $strHighLight = $self->{oManifest}->variableReplace($oExecute->fieldGet('exe-highlight', false));
my $bHighLightFound = false;
if (defined($strOutput))
{
my $strHighLight = $self->{oSite}->variableReplace($oExecute->fieldGet('exe-highlight', false));
my $bHighLightOld;
my $bHighLightFound = false;
my $strHighLightOutput;
if ($oExecute->fieldTest('exe-highlight-type', 'error'))
{
$bExeExpectedError = true;
}
foreach my $strLine (split("\n", $strOutput))
{
my $bHighLight = defined($strHighLight) && $strLine =~ /$strHighLight/;
@ -407,13 +300,14 @@ sub sectionProcess
if (defined($bHighLightOld) && $bHighLight != $bHighLightOld)
{
$oExecuteBodyElement->
addNew(HTML_DIV, 'execute-body-output' . ($bHighLightOld ? '-highlight' : '') .
($bExeExpectedError ? '-error' : ''), {strContent => $strHighLightOutput});
addNew(HTML_PRE, 'execute-body-output' .
($bHighLightOld ? '-highlight' . ($bExeExpectedError ? '-error' : '') : ''),
{strContent => $strHighLightOutput, bPre => true});
undef($strHighLightOutput);
}
$strHighLightOutput .= "${strLine}\n";
$strHighLightOutput .= (defined($strHighLightOutput) ? "\n" : '') . $strLine;
$bHighLightOld = $bHighLight;
$bHighLightFound = $bHighLightFound ? true : $bHighLight ? true : false;
@ -422,19 +316,18 @@ sub sectionProcess
if (defined($bHighLightOld))
{
$oExecuteBodyElement->
addNew(HTML_DIV, 'execute-body-output' . ($bHighLightOld ? '-highlight' : ''),
{strContent => $strHighLightOutput});
undef($strHighLightOutput);
}
if ($self->{bExe} && defined($strHighLight) && !$bHighLightFound)
{
confess &log(ERROR, "unable to find a match for highlight: ${strHighLight}");
addNew(HTML_PRE, 'execute-body-output' .
($bHighLightOld ? '-highlight' . ($bExeExpectedError ? '-error' : '') : ''),
{strContent => $strHighLightOutput, bPre => true});
}
$bFirst = true;
}
if ($self->{bExe} && $self->isRequired($oSection) && defined($strHighLight) && !$bHighLightFound)
{
confess &log(ERROR, "unable to find a match for highlight: ${strHighLight}");
}
}
$bFirst = false;
@ -452,7 +345,7 @@ sub sectionProcess
{
$oSectionBodyElement->
addNew(HTML_DIV, 'section-body-text',
{strContent => $oRender->processText($oChild->textGet())});
{strContent => $self->processText($oChild->textGet())});
}
# Add option descriptive text
elsif ($oChild->nameGet() eq 'option-description')
@ -467,17 +360,27 @@ sub sectionProcess
$oSectionBodyElement->
addNew(HTML_DIV, 'section-body-text',
{strContent => $oRender->processText($oDescription)});
{strContent => $self->processText($oDescription)});
}
# Add/remove backrest config options
elsif ($oChild->nameGet() eq 'backrest-config')
{
$oSectionBodyElement->add($self->backrestConfigProcess($oChild, $iDepth));
my $oConfigElement = $self->backrestConfigProcess($oSection, $oChild, $iDepth + 3);
if (defined($oConfigElement))
{
$oSectionBodyElement->add($oConfigElement);
}
}
# Add/remove postgres config options
elsif ($oChild->nameGet() eq 'postgres-config')
{
$oSectionBodyElement->add($self->postgresConfigProcess($oChild, $iDepth));
my $oConfigElement = $self->postgresConfigProcess($oSection, $oChild, $iDepth + 3);
if (defined($oConfigElement))
{
$oSectionBodyElement->add($oConfigElement);
}
}
# Add a subsection
elsif ($oChild->nameGet() eq 'section')
@ -488,10 +391,10 @@ sub sectionProcess
$oSectionBodyElement->add($oChildSectionElement);
$oSectionTocElement->add($oChildSectionTocElement);
}
# Skip children that have already been processed and error on others
elsif ($oChild->nameGet() ne 'title')
# Check if the child can be processed by a parent
else
{
confess &log(ASSERT, 'unable to find child type ' . $oChild->nameGet());
$self->sectionChildProcess($oSection, $oChild, $iDepth + 1);
}
}
@ -515,69 +418,45 @@ sub backrestConfigProcess
my
(
$strOperation,
$oSection,
$oConfig,
$iDepth
) =
logDebugParam
(
OP_DOC_HTML_PAGE_BACKREST_CONFIG_PROCESS, \@_,
{name => 'oSection'},
{name => 'oConfig'},
{name => 'iDepth'}
);
# Get filename
my $strFile = $self->{oSite}->variableReplace($oConfig->paramGet('file'));
# Generate the config
my $oConfigElement;
my ($strFile, $strConfig, $bShow) = $self->backrestConfig($oSection, $oConfig, $iDepth);
&log(INFO, (' ' x $iDepth) . 'process backrest config: ' . $strFile);
foreach my $oOption ($oConfig->nodeList('backrest-config-option'))
if ($bShow)
{
my $strSection = $oOption->fieldGet('backrest-config-option-section');
my $strKey = $oOption->fieldGet('backrest-config-option-key');
my $strValue = $self->{oSite}->variableReplace(trim($oOption->fieldGet('backrest-config-option-value'), false));
my $strHostName = $self->{oManifest}->variableReplace($oConfig->paramGet('host'));
if (!defined($strValue))
{
delete(${$self->{config}}{$strFile}{$strSection}{$strKey});
# Render the config
$oConfigElement = new BackRestDoc::Html::DocHtmlElement(HTML_DIV, "config");
if (keys(${$self->{config}}{$strFile}{$strSection}) == 0)
{
delete(${$self->{config}}{$strFile}{$strSection});
}
$oConfigElement->
addNew(HTML_DIV, "config-title",
{strContent => "<span class=\"host\">${strHostName}</span>:<span class=\"file\">${strFile}</span>" .
" <b>&#x21d2;</b> " . $self->processText($oConfig->nodeGet('title')->textGet())});
&log(INFO, (' ' x ($iDepth + 1)) . "reset ${strSection}->${strKey}");
}
else
{
${$self->{config}}{$strFile}{$strSection}{$strKey} = $strValue;
&log(INFO, (' ' x ($iDepth + 1)) . "set ${strSection}->${strKey} = ${strValue}");
}
my $oConfigBodyElement = $oConfigElement->addNew(HTML_DIV, "config-body");
#
# $oConfigBodyElement->
# addNew(HTML_DIV, "config-body-title",
# {strContent => "${strFile}:"});
$oConfigBodyElement->
addNew(HTML_DIV, "config-body-output",
{strContent => $strConfig});
}
# Save the ini file
executeTest("sudo chmod 777 $strFile", {bSuppressError => true});
iniSave($strFile, $self->{config}{$strFile}, true);
# Generate config element
my $oConfigElement = new BackRestDoc::Html::DocHtmlElement(HTML_DIV, "config");
$oConfigElement->
addNew(HTML_DIV, "config-title",
{strContent => $self->{oRender}->processText($oConfig->nodeGet('title')->textGet()) . ':'});
my $oConfigBodyElement = $oConfigElement->addNew(HTML_DIV, "config-body");
$oConfigBodyElement->
addNew(HTML_DIV, "config-body-title",
{strContent => "${strFile}:"});
$oConfigBodyElement->
addNew(HTML_DIV, "config-body-output",
{strContent => fileStringRead($strFile)});
executeTest("sudo chown postgres:postgres $strFile");
executeTest("sudo chmod 640 $strFile");
# Return from function and log return values if any
return logDebugReturn
(
@ -597,97 +476,46 @@ sub postgresConfigProcess
my
(
$strOperation,
$oSection,
$oConfig,
$iDepth
) =
logDebugParam
(
OP_DOC_HTML_PAGE_POSTGRES_CONFIG_PROCESS, \@_,
{name => 'oSection'},
{name => 'oConfig'},
{name => 'iDepth'}
);
# Get filename
my $strFile = $self->{oSite}->variableReplace($oConfig->paramGet('file'));
# Generate the config
my $oConfigElement;
my ($strFile, $strConfig, $bShow) = $self->postgresConfig($oSection, $oConfig, $iDepth);
if (!defined(${$self->{'pg-config'}}{$strFile}{base}))
if ($bShow)
{
${$self->{'pg-config'}}{$strFile}{base} = fileStringRead($strFile);
# Render the config
my $strHostName = $self->{oManifest}->variableReplace($oConfig->paramGet('host'));
$oConfigElement = new BackRestDoc::Html::DocHtmlElement(HTML_DIV, "config");
$oConfigElement->
addNew(HTML_DIV, "config-title",
{strContent => "<span class=\"host\">${strHostName}</span>:<span class=\"file\">${strFile}</span>" .
" <b>&#x21d2;</b> " . $self->processText($oConfig->nodeGet('title')->textGet())});
my $oConfigBodyElement = $oConfigElement->addNew(HTML_DIV, "config-body");
# $oConfigBodyElement->
# addNew(HTML_DIV, "config-body-title",
# {strContent => "append to ${strFile}:"});
$oConfigBodyElement->
addNew(HTML_DIV, "config-body-output",
{strContent => defined($strConfig) ? $strConfig : '<No PgBackRest Settings>'});
$oConfig->fieldSet('actual-config', $strConfig);
}
my $oConfigHash = $self->{'pg-config'}{$strFile};
my $oConfigHashNew;
if (!defined($$oConfigHash{old}))
{
$oConfigHashNew = {};
$$oConfigHash{old} = {}
}
else
{
$oConfigHashNew = dclone($$oConfigHash{old});
}
&log(INFO, (' ' x $iDepth) . 'process postgres config: ' . $strFile);
foreach my $oOption ($oConfig->nodeList('postgres-config-option'))
{
my $strKey = $oOption->paramGet('key');
my $strValue = $self->{oSite}->variableReplace(trim($oOption->valueGet()));
if ($strValue eq '')
{
delete($$oConfigHashNew{$strKey});
&log(INFO, (' ' x ($iDepth + 1)) . "reset ${strKey}");
}
else
{
$$oConfigHashNew{$strKey} = $strValue;
&log(INFO, (' ' x ($iDepth + 1)) . "set ${strKey} = ${strValue}");
}
}
# Generate config text
my $strConfig;
foreach my $strKey (sort(keys(%$oConfigHashNew)))
{
if (defined($strConfig))
{
$strConfig .= "\n";
}
$strConfig .= "${strKey} = $$oConfigHashNew{$strKey}";
}
# Save the conf file
executeTest("sudo chown vagrant $strFile");
fileStringWrite($strFile, $$oConfigHash{base} .
(defined($strConfig) ? "\n# pgBackRest Configuration\n${strConfig}" : ''));
executeTest("sudo chown postgres $strFile");
# Generate config element
my $oConfigElement = new BackRestDoc::Html::DocHtmlElement(HTML_DIV, "config");
$oConfigElement->
addNew(HTML_DIV, "config-title",
{strContent => $self->{oRender}->processText($oConfig->nodeGet('title')->textGet()) . ':'});
my $oConfigBodyElement = $oConfigElement->addNew(HTML_DIV, "config-body");
$oConfigBodyElement->
addNew(HTML_DIV, "config-body-title",
{strContent => "append to ${strFile}:"});
$oConfigBodyElement->
addNew(HTML_DIV, "config-body-output",
{strContent => defined($strConfig) ? $strConfig : '<No PgBackRest Settings>'});
$$oConfigHash{old} = $oConfigHashNew;
# Return from function and log return values if any
return logDebugReturn
(

View File

@ -25,6 +25,7 @@ use lib dirname($0) . '/../test/lib';
use BackRestTest::Common::ExecuteTest;
use BackRestDoc::Common::DocConfig;
use BackRestDoc::Common::DocManifest;
use BackRestDoc::Html::DocHtmlPage;
####################################################################################################################################
@ -51,23 +52,19 @@ sub new
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{oRender},
$self->{oReference},
$self->{oManifest},
$self->{strXmlPath},
$self->{strHtmlPath},
$self->{strCssFile},
$self->{strHtmlRoot},
$self->{bExe}
) =
logDebugParam
(
OP_DOC_HTML_SITE_NEW, \@_,
{name => 'oRender'},
{name => 'oReference'},
{name => 'oManifest'},
{name => 'strXmlPath'},
{name => 'strHtmlPath'},
{name => 'strCssFile'},
{name => 'strHtmlRoot'},
{name => 'bExe'}
);
@ -83,74 +80,6 @@ sub new
or confess &log(ERROR, "unable to create path $self->{strHtmlPath}");
}
# Create the footer
$self->{strFooter} = 'Copyright © 2015' . (strftime('%Y', localtime) ne '2015' ? '-' . strftime('%Y', localtime) : '') .
', The PostgreSQL Global Development Group, <a href="{[github-url-license]}">MIT License</a>. Updated ' .
strftime('%B ', localtime) . trim(strftime('%e,', localtime)) . strftime(' %Y.', localtime);
# Insert pages into the site hash
$self->{oSite} =
{
'common' =>
{
'oRender' => $self->{oRender},
'oReference' => $self->{oReference},
'strFooter' => $self->{strFooter}
},
'page' =>
{
'index' =>
{
strMenuTitle => 'Home',
oDoc => new BackRestDoc::Common::Doc("$self->{strXmlPath}/index.xml")
},
'command' =>
{
strMenuTitle => 'Commands',
oDoc => $self->{oReference}->helpCommandDocGet()
},
'configuration' =>
{
strMenuTitle => 'Configuration',
oDoc => $self->{oReference}->helpConfigDocGet()
# }
},
'user-guide' =>
{
strMenuTitle => 'User Guide',
oDoc => new BackRestDoc::Common::Doc("$self->{strXmlPath}/user-guide.xml")
}
}
};
# Create common variables
${$self->{var}}{version} = $VERSION;
${$self->{var}}{'backrest-exe'} = $self->{oRender}->{strExeName};
${$self->{var}}{'postgres'} = 'PostgreSQL';
${$self->{var}}{'backrest-url-root'} = $self->{strHtmlRoot};
${$self->{var}}{'dash'} = '-';
# Read variables from pages
foreach my $strPageId (sort(keys(${$self->{oSite}}{page})))
{
my $oPage = ${$self->{oSite}}{page}{$strPageId};
if (defined($$oPage{oDoc}->nodeGet('variable-list', false)))
{
foreach my $oVariable ($$oPage{oDoc}->nodeGet('variable-list')->nodeList('variable'))
{
my $strName = $oVariable->fieldGet('variable-name');
my $strValue = $oVariable->fieldGet('variable-value');
${$self->{var}}{$strName} = $self->variableReplace($strValue);
}
}
}
# Return from function and log return values if any
return logDebugReturn
(
@ -159,58 +88,6 @@ sub new
);
}
####################################################################################################################################
# variableReplace
#
# Replace variables in the string.
####################################################################################################################################
sub variableReplace
{
my $self = shift;
my $strBuffer = shift;
if (!defined($strBuffer))
{
return undef;
}
foreach my $strName (sort(keys(%{$self->{var}})))
{
my $strValue = $self->{var}{$strName};
$strBuffer =~ s/\{\[$strName\]\}/$strValue/g;
}
return $strBuffer;
}
####################################################################################################################################
# variableSet
#
# Set a variable to be replaced later.
####################################################################################################################################
sub variableSet
{
my $self = shift;
my $strKey = shift;
my $strValue = shift;
${$self->{var}}{$strKey} = $strValue;
}
####################################################################################################################################
# variableGet
#
# Get the current value of a variable.
####################################################################################################################################
sub variableGet
{
my $self = shift;
my $strKey = shift;
return ${$self->{var}}{$strKey};
}
####################################################################################################################################
# process
#
@ -228,14 +105,14 @@ sub process
copy($self->{strCssFile}, $strCssFileDestination)
or confess &log(ERROR, "unable to copy $self->{strCssFile} to ${strCssFileDestination}");
# Render pages
my $oSite = $self->{oSite};
foreach my $strPageId (sort(keys($$oSite{'page'})))
foreach my $strPageId ($self->{oManifest}->renderOutList(RENDER_TYPE_HTML))
{
&log(INFO, " render out: ${strPageId}");
# Save the html page
fileStringWrite("$self->{strHtmlPath}/${strPageId}.html",
$self->variableReplace((new BackRestDoc::Html::DocHtmlPage($self, $strPageId, $self->{bExe}))->process()),
$self->{oManifest}->variableReplace((new BackRestDoc::Html::DocHtmlPage($self->{oManifest},
$strPageId, $self->{bExe}))->process()),
false);
}

View File

@ -0,0 +1,137 @@
####################################################################################################################################
# DOC LATEX MODULE
####################################################################################################################################
package BackRestDoc::Latex::DocLatex;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Data::Dumper;
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use File::Copy;
use POSIX qw(strftime);
use Storable qw(dclone);
use lib dirname($0) . '/../lib';
use BackRest::Common::Log;
use BackRest::Common::String;
use BackRest::FileCommon;
use BackRest::Version;
use lib dirname($0) . '/../test/lib';
use BackRestTest::Common::ExecuteTest;
use BackRestDoc::Common::DocConfig;
use BackRestDoc::Common::DocManifest;
use BackRestDoc::Latex::DocLatexSection;
####################################################################################################################################
# Operation constants
####################################################################################################################################
use constant OP_DOC_LATEX => 'DocLatex';
use constant OP_DOC_LATEX_NEW => OP_DOC_LATEX . '->new';
use constant OP_DOC_LATEX_PROCESS => OP_DOC_LATEX . '->process';
####################################################################################################################################
# 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->{oManifest},
$self->{strXmlPath},
$self->{strLatexPath},
$self->{strPreambleFile},
$self->{bExe}
) =
logDebugParam
(
OP_DOC_LATEX_NEW, \@_,
{name => 'oManifest'},
{name => 'strXmlPath'},
{name => 'strLatexPath'},
{name => 'strPreambleFile'},
{name => 'bExe'}
);
# Remove the current html path if it exists
if (-e $self->{strLatexPath})
{
executeTest("rm -rf $self->{strLatexPath}/*");
}
# Else create the html path
else
{
mkdir($self->{strLatexPath})
or confess &log(ERROR, "unable to create path $self->{strLatexPath}");
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# process
#
# Generate the site html
####################################################################################################################################
sub process
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(OP_DOC_LATEX_PROCESS);
my $oRender = $self->{oManifest}->renderGet(RENDER_TYPE_PDF);
# Copy the logo
copy('/backrest/doc/resource/latex/cds-logo.eps', "$self->{strLatexPath}/logo.eps")
or confess &log(ERROR, "unable to copy logo");
my $strLatex = $self->{oManifest}->variableReplace(fileStringRead($self->{strPreambleFile}), 'latex') . "\n";
foreach my $strPageId ($self->{oManifest}->renderOutList(RENDER_TYPE_PDF))
{
&log(INFO, " render out: ${strPageId}");
my $oDocLatexSection =
new BackRestDoc::Latex::DocLatexSection($self->{oManifest}, $strPageId, $self->{bExe});
# Save the html page
$strLatex .= $oDocLatexSection->process();
}
$strLatex .= "\n% " . ('-' x 130) . "\n% End document\n% " . ('-' x 130) . "\n\\end{document}\n";
my $strLatexFileName = $self->{oManifest}->variableReplace("$self->{strLatexPath}/" . $$oRender{file} . '.tex');
fileStringWrite($strLatexFileName, $strLatex, false);
executeTest("pdflatex -output-directory=$self->{strLatexPath} -shell-escape $strLatexFileName",
{bSuppressStdErr => true});
executeTest("pdflatex -output-directory=$self->{strLatexPath} -shell-escape $strLatexFileName",
{bSuppressStdErr => true});
# Return from function and log return values if any
logDebugReturn($strOperation);
}
1;

View File

@ -0,0 +1,417 @@
####################################################################################################################################
# DOC LATEX SECTION MODULE
####################################################################################################################################
package BackRestDoc::Latex::DocLatexSection;
use parent 'BackRestDoc::Common::DocExecute';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Data::Dumper;
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use File::Copy;
use Storable qw(dclone);
use lib dirname($0) . '/../lib';
use BackRest::Common::Ini;
use BackRest::Common::Log;
use BackRest::Common::String;
use BackRest::Config::ConfigHelp;
use BackRest::FileCommon;
use BackRestDoc::Common::DocManifest;
####################################################################################################################################
# Operation constants
####################################################################################################################################
use constant OP_DOC_LATEX_SECTION => 'DocLatexSection';
use constant OP_DOC_LATEX_SECTION_CONFIG_PROCESS => OP_DOC_LATEX_SECTION . '->configProcess';
use constant OP_DOC_LATEX_SECTION_NEW => OP_DOC_LATEX_SECTION . '->new';
use constant OP_DOC_LATEX_SECTION_PROCESS => OP_DOC_LATEX_SECTION . '->process';
use constant OP_DOC_LATEX_SECTION_SECTION_PROCESS => OP_DOC_LATEX_SECTION . '->sectionProcess';
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift; # Class name
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oManifest,
$strRenderOutKey,
$bExe
) =
logDebugParam
(
OP_DOC_LATEX_SECTION_NEW, \@_,
{name => 'oManifest'},
{name => 'strRenderOutKey'},
{name => 'bExe'}
);
# Create the class hash
my $self = $class->SUPER::new('latex', $oManifest, $strRenderOutKey, $bExe);
bless $self, $class;
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# process
#
# Generate the site html
####################################################################################################################################
sub process
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my $strOperation = logDebugParam(OP_DOC_LATEX_SECTION_PROCESS);
# Working variables
my $oPage = $self->{oDoc};
my $strLatex;
# Initialize page
my $strTitle = "{[project]}" .
(defined($oPage->paramGet('title', false)) ? ' ' . $oPage->paramGet('title') : '');
my $strSubTitle = $oPage->paramGet('subtitle', false);
# Render sections
foreach my $oSection ($oPage->nodeList('section'))
{
$strLatex .= (defined($strLatex) ? "\n" : '') . $self->sectionProcess($oSection, undef, 1);
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strHtml', value => $strLatex, trace => true}
);
}
####################################################################################################################################
# sectionProcess
####################################################################################################################################
sub sectionProcess
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oSection,
$strSection,
$iDepth
) =
logDebugParam
(
OP_DOC_LATEX_SECTION_SECTION_PROCESS, \@_,
{name => 'oSection'},
{name => 'strSection', required => false},
{name => 'iDepth'}
);
&log($iDepth == 1 ? INFO : DEBUG, (' ' x ($iDepth + 1)) . 'process section: ' . $oSection->paramGet('id'));
# Create the section
my $strSectionTitle = $self->processText($oSection->nodeGet('title')->textGet());
$strSection .= (defined($strSection) ? ', ' : '') . "'${strSectionTitle}' " . ('Sub' x ($iDepth - 1)) . "Section";
my $strLatex =
"% ${strSection}\n% " . ('-' x 130) . "\n\\";
if ($iDepth <= 3)
{
$strLatex .= ($iDepth > 1 ? ('sub' x ($iDepth - 1)) : '') . "section";
}
elsif ($iDepth == 4)
{
$strLatex .= 'paragraph';
}
else
{
confess &log(ASSERT, "section depth of ${iDepth} exceeds maximum");
}
$strLatex .= "\{${strSectionTitle}\}\n";
foreach my $oChild ($oSection->nodeList())
{
&log(DEBUG, (' ' x ($iDepth + 2)) . 'process child ' . $oChild->nameGet());
# Execute a command
if ($oChild->nameGet() eq 'execute-list')
{
my $strHostName = $self->{oManifest}->variableReplace($oChild->paramGet('host'));
$strLatex .=
"\n\\begin\{lstlisting\}[title=\{\\textnormal{\\textbf\{${strHostName}}} --- " .
$self->processText($oChild->nodeGet('title')->textGet()) . "}]\n";
foreach my $oExecute ($oChild->nodeList('execute'))
{
my $bExeShow = !$oExecute->paramTest('show', 'n');
my ($strCommand, $strOutput) = $self->execute($oSection,
$self->{oManifest}->variableReplace($oChild->paramGet('host')),
$oExecute, $iDepth + 3);
if ($bExeShow)
{
$strLatex .= "${strCommand}\n";
if (defined($strOutput))
{
$strLatex .= "\nOutput:\n\n${strOutput}\n";
}
}
}
$strLatex .=
"\\end{lstlisting}\n";
}
# Add code block
elsif ($oChild->nameGet() eq 'code-block')
{
my $strTitle = $oChild->paramGet("title", false);
if (defined($strTitle) && $strTitle eq '')
{
undef($strTitle)
}
# Begin the code listing
if (!defined($strTitle))
{
$strLatex .=
"\\vspace{.75em}\n";
}
$strLatex .=
"\\begin\{lstlisting\}";
# Add the title if one is provided
if (defined($strTitle))
{
$strLatex .= "[title=\{${strTitle}:\}]";
}
# End the code listing
$strLatex .=
"\n" .
trim($oChild->valueGet()) . "\n" .
"\\end{lstlisting}\n";
}
# Add table
elsif ($oChild->nameGet() eq 'table')
{
my $oHeader = $oChild->nodeGet('table-header');
my @oyColumn = $oHeader->nodeList('table-column');
my $strWidth = '{' . ($oHeader->paramTest('width') ? $oHeader->paramGet('width') : '\textwidth') . '}';
# Build the table header
$strLatex .= "\\vspace{1em}\\newline\n";
$strLatex .= "\\begin{tabularx}${strWidth}{ | ";
foreach my $oColumn (@oyColumn)
{
my $strAlignCode;
my $strAlign = $oColumn->paramGet("align", false);
if ($oColumn->paramTest('fill', 'y'))
{
if (!defined($strAlign) || $strAlign eq 'left')
{
$strAlignCode = 'X';
}
elsif ($strAlign eq 'right')
{
$strAlignCode = 'R';
}
else
{
confess &log(ERROR, "align '${strAlign}' not valid when fill=y");
}
}
else
{
if (!defined($strAlign) || $strAlign eq 'left')
{
$strAlignCode = 'l';
}
elsif ($strAlign eq 'center')
{
$strAlignCode = 'c';
}
elsif ($strAlign eq 'right')
{
$strAlignCode = 'r';
}
else
{
confess &log(ERROR, "align '${strAlign}' not valid");
}
}
# $strLatex .= 'p{' . $oColumn->paramGet("width") . '} | ';
$strLatex .= $strAlignCode . ' | ';
}
$strLatex .= "}\n";
if ($oChild->nodeGet("title", false))
{
$strLatex .= "\\caption{" . $self->processText($oChild->nodeGet("title")->textGet()) . ":}\\\\\n";
}
$strLatex .= "\\hline";
$strLatex .= "\\rowcolor{ltgray}\n";
my $strLine;
foreach my $oColumn (@oyColumn)
{
$strLine .= (defined($strLine) ? ' & ' : '') . '\textbf{' . $self->processText($oColumn->textGet()) . '}';
}
$strLatex .= "${strLine}\\\\";
# Build the rows
foreach my $oRow ($oChild->nodeGet('table-data')->nodeList('table-row'))
{
$strLatex .= "\\hline\n";
undef($strLine);
foreach my $oRowCell ($oRow->nodeList('table-cell'))
{
$strLine .= (defined($strLine) ? ' & ' : '') . $self->processText($oRowCell->textGet());
}
$strLatex .= "${strLine}\\\\";
}
$strLatex .= "\\hline\n\\end{tabularx}\n";
}
# Add descriptive text
elsif ($oChild->nameGet() eq 'p')
{
$strLatex .= "\n" . $self->processText($oChild->textGet()) . "\n";
}
# Add option descriptive text
elsif ($oChild->nameGet() eq 'option-description')
{
my $strOption = $oChild->paramGet("key");
my $oDescription = ${$self->{oReference}->{oConfigHash}}{&CONFIG_HELP_OPTION}{$strOption}{&CONFIG_HELP_DESCRIPTION};
if (!defined($oDescription))
{
confess &log(ERROR, "unable to find ${strOption} option in sections - try adding command?");
}
$strLatex .= "\n" . $self->processText($oDescription) . "\n";
}
# Add/remove config options
elsif ($oChild->nameGet() eq 'backrest-config' || $oChild->nameGet() eq 'postgres-config')
{
$strLatex .= $self->configProcess($oSection, $oChild, $iDepth + 3);
}
# Add a subsection
elsif ($oChild->nameGet() eq 'section')
{
$strLatex .= "\n" . $self->sectionProcess($oChild, $strSection, $iDepth + 1);
}
# Check if the child can be processed by a parent
else
{
$self->sectionChildProcess($oSection, $oChild, $iDepth + 1);
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strSection', value => $strLatex, trace => true}
);
}
####################################################################################################################################
# configProcess
####################################################################################################################################
sub configProcess
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oSection,
$oConfig,
$iDepth
) =
logDebugParam
(
OP_DOC_LATEX_SECTION_CONFIG_PROCESS, \@_,
{name => 'oSection'},
{name => 'oConfig'},
{name => 'iDepth'}
);
# Working variables
my $strLatex = '';
my $strFile;
my $strConfig;
my $bShow = true;
# Generate the config
if ($oConfig->nameGet() eq 'backrest-config')
{
($strFile, $strConfig, $bShow) = $self->backrestConfig($oSection, $oConfig, $iDepth);
}
else
{
($strFile, $strConfig, $bShow) = $self->postgresConfig($oSection, $oConfig, $iDepth);
}
if ($bShow)
{
my $strHostName = $self->{oManifest}->variableReplace($oConfig->paramGet('host'));
# Replace _ in filename
$strFile = $self->variableReplace($strFile);
# Render the config
$strLatex =
"\n\\begin\{lstlisting\}[title=\{\\textnormal{\\textbf\{${strHostName}}}:\\textnormal{\\texttt\{${strFile}}} --- " .
$self->processText($oConfig->nodeGet('title')->textGet()) . "}]\n" .
${strConfig} .
"\\end{lstlisting}\n";
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strConfig', value => $strLatex, trace => true}
);
}
1;

46
doc/manifest.xml Normal file
View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE doc SYSTEM "manifest.dtd">
<doc>
<!-- System-wide variables -->
<variable-list>
<variable key="project">pgBackRest</variable>
<variable key="version" eval="y">use BackRest::Version; $VERSION</variable>
<variable key="project-exe">pg_backrest</variable>
<variable key="project-url-root">/</variable>
<variable key="postgres">PostgreSQL</variable>
<variable key="dash">-</variable>
<!-- Logo locations -->
<variable key="logo">/backrest/doc/output/latex/logo</variable>
<!-- HTML variables -->
<variable key="html-footer" eval='y'>
use POSIX qw(strftime); 'Copyright &amp;copy; 2015' . (strftime('%Y', localtime) ne '2015' ? '-' . strftime('%Y', localtime) : '') .
', The PostgreSQL Global Development Group, &lt;a href="{[github-url-license]}">MIT License&lt;/a>. Updated ' .
strftime('%B ', localtime) . trim(strftime('%e,', localtime)) . strftime(' %Y.', localtime)</variable>
<!-- PDF variables -->
<variable key="pdf-title">{[project]} User Guide</variable>
<variable key="pdf-subtitle">Open Source PostgreSQL Backup and Restore Utility</variable>
<variable key="pdf-file">CrunchyBackRest-UserGuide-{[version]}</variable>
</variable-list>
<source-list>
<source key="index"/>
<source key="user-guide"/>
<source key="reference" type="custom"/>
</source-list>
<render-list>
<render type="html">
<render-source key="index" menu="Home"/>
<render-source key="user-guide" menu="User Guide"/>
<render-source key="configuration" source="reference" menu="Configuration"/>
<render-source key="command" source="reference" menu="Commands"/>
</render>
<render type="pdf" file="{[pdf-file]}">
<render-source key="user-guide"/>
</render>
</render-list>
</doc>

View File

@ -17,6 +17,7 @@ body
margin: 0px auto;
padding: 0px;
width: 100%;
text-align: justify;
}
@media (min-width: 1000px)
@ -43,7 +44,6 @@ Header
{
width:100%;
text-align:center;
/*float:left;*/
}
.page-header-title
@ -63,24 +63,19 @@ Header
/*******************************************************************************
Menu
*******************************************************************************/
.page-menu
{
/*margin-top: .25em;*/
/*font-size: 14pt;*/
}
/*.page-menu*/
.menu-body
{
text-align: center;
/*font-weight: 600;*/
/*border-bottom: 2px #dddddd solid;*/
}
.menu
{
white-space: nowrap;
display: inline;
margin-left: 6px;
margin-left: .5em;
font-size: 13pt;
}
.menu:first-of-type
@ -91,20 +86,14 @@ Menu
.menu:before
{
content: "[";
/*margin-right: .5em;*/
}
.menu:after
{
content: "]";
/*margin-left: .5em;*/
}
.menu-link
{
/*margin-left: 2px;
margin-right: 2px;*/
}
/*.menu-link*/
a.menu-link:link, a.menu-link:visited, a.menu-link:active
{
@ -157,7 +146,7 @@ Section
*******************************************************************************/
.section1
{
margin-top: 2em;
margin-top: 2.5em;
}
.section1:first-of-type
@ -178,7 +167,9 @@ Section
.section1-title, .page-toc-title
{
background-color: #dddddd;
border-radius: 3px;
background-color: #396a93;
color: #f8f8f8;
font-size: 22pt;
padding-left: .5em;
margin-bottom: .5em;
@ -191,7 +182,8 @@ Section
.section2-title
{
border-bottom: 2px #cccccc solid;
border-bottom: 2px #396a93 solid;
color: #396a93;
font-size: 16pt;
}
@ -199,7 +191,6 @@ Section
{
margin-left: 1em;
margin-top: 1em;
/*border-top: 1px #cccccc solid;*/
}
.section3:first-of-type
@ -211,8 +202,8 @@ Section
{
display: inline;
font-size: 14pt;
/*text-decoration: underline;*/
border-bottom: 1px #bbbbbb solid;
color: #396a93;
border-bottom: 1px #396a93 solid;
}
.section-intro
@ -220,9 +211,7 @@ Section
margin-top: .75em;
}
.section-body
{
}
/*.section-body*/
/*******************************************************************************
Config, Execute, and Code Block Elements
@ -244,7 +233,7 @@ Config, Execute, and Code Block Elements
/*white-space: nowrap;*/
margin-left: 1em;
margin-right: 1em;
margin-top: 1em;
margin-top: .8em;
margin-bottom: 1em;
}
@ -259,15 +248,16 @@ Config, Execute, and Code Block Elements
.config-body,
.code-block
{
border-radius: 3px;
background-color: #444444;
/*overflow-x:auto;*/
font-family: monospace;
unicode-bidi: embed;
color: white;
color: #f8f8f8;
/*color: white;*/
font-size: 10pt;
}
.execute-body-cmd-first, .execute-body-cmd,
.execute-body-cmd,
.execute-body-output, .execute-body-output-highlight,
.config-body-title,
.code-block
@ -276,16 +266,31 @@ Config, Execute, and Code Block Elements
padding-right: .5em;
}
.execute-body-cmd-first, .execute-body-output,
/* Would rather not use pre at all, but Firefox won't copy code samples correctly without it */
pre
{
margin: 0px;
padding: 0px;
white-space: pre-wrap;
}
.execute-body-cmd:before
{
content: "$ ";
}
.execute-body-cmd:first-of-type, .execute-body-output,
.config-body-title, .config-body-output,
.code-block
.code-block,
.execute-body-output-highlight, .execute-body-output-highlight-error
{
padding-top: .25em;
}
.execute-body-cmd-first, .execute-body-cmd, .execute-body-output,
.execute-body-cmd, .execute-body-output,
.config-body-title, .config-body-output,
.code-block
.code-block,
.execute-body-output-highlight, .execute-body-output-highlight-error
{
padding-bottom: .25em;
}
@ -294,32 +299,31 @@ Config, Execute, and Code Block Elements
.config-body-output,
.code-block
{
border-radius: 3px;
background-color: #606060;
}
.execute-body-output-highlight
{
background-color: green;
border-radius: 3px;
background-color: #1e7b1e;
}
.execute-body-output-highlight-error
{
background-color: firebrick;
border-radius: 3px;
background-color: #a32929;
}
.execute-body-output, .execute-body-output-highlight, .execute-body-output-highlight-error,
.config-body-output
{
padding-left: 2em;
padding-left: 1.75em;
}
.code-block
{
}
/*.code-block*/
.section-body-execute-output
{
}
/*.section-body-execute-output*/
/*******************************************************************************
Keywords
@ -329,14 +333,16 @@ Keywords
font-weight: 500;
}
.host
{
font-weight: 700;
}
.path, .br-option, .br-setting, .pg-option, .pg-setting, .id, .user, .file, .path
{
/*font-weight: 600;*/
/*background-color: #e8e8e8;*/
unicode-bidi: embed;
font-family: monospace;
white-space: nowrap;
/*font-size: 11pt;*/
}
/*******************************************************************************

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,135 @@
% ----------------------------------------------------------------------------------------------------------------------------------
% pgBackRest User Guide
% ----------------------------------------------------------------------------------------------------------------------------------
\documentclass[letterpaper,12pt]{article}
% Add hyperlinks to TOC
% ----------------------------------------------------------------------------------------------------------------------------------
\usepackage{hyperref}
% Allow EPS files
% ----------------------------------------------------------------------------------------------------------------------------------
\usepackage{graphicx}
\usepackage{epstopdf}
% Use the caption package to enable captions that are not numbered (caption*)
% ----------------------------------------------------------------------------------------------------------------------------------
\usepackage[font=small,textfont=it,justification=justified,singlelinecheck=false]{caption}
% Create a light gray color to use for source code listings
% ----------------------------------------------------------------------------------------------------------------------------------
\usepackage[table]{xcolor}
\definecolor{ltgray}{HTML}{E8E8E8}
% Use listings package instead of verbatim for displaying code
% ----------------------------------------------------------------------------------------------------------------------------------
\usepackage{courier}
\usepackage{textcomp}
\usepackage{listings}
\lstset
{
basicstyle=\small\ttfamily,
columns=flexible,
breaklines=true,
frame=tb,
backgroundcolor=\color{ltgray},
upquote=true
}
% Use tabularx for tables
% ----------------------------------------------------------------------------------------------------------------------------------
\usepackage{tabularx}
\newcolumntype{R}{>{\raggedleft\arraybackslash}X}%
\renewcommand{\arraystretch}{1.3}
% \usepackage{ltablex}
% Allow four section levels (The fourth is implemented with paragraph)
% ----------------------------------------------------------------------------------------------------------------------------------
\usepackage{titlesec}
\setcounter{secnumdepth}{4}
\titleformat{\paragraph}
{\normalfont\normalsize\bfseries}{\theparagraph}{1em}{}
\titlespacing*{\paragraph}{0pt}{3.25ex plus 1ex minus .2ex}{1.5ex plus .2ex}
% Define source code highlighting
% ----------------------------------------------------------------------------------------------------------------------------------
\newcommand{\Hilight}{\makebox[0pt][l]{\color{cyan}\rule[-4pt]{0.65\linewidth}{14pt}}}
% Set the font to Helvetica
% ----------------------------------------------------------------------------------------------------------------------------------
\usepackage{helvet}
\renewcommand{\familydefault}{\sfdefault}
% Set margins
% ----------------------------------------------------------------------------------------------------------------------------------
\usepackage[top=.9in, bottom=1in, left=.5in, right=.5in]{geometry}
% Sections start a new page
% ----------------------------------------------------------------------------------------------------------------------------------
\let\stdsection\section
\renewcommand\section{\newpage\stdsection}
% Format paragraphs with no indent and a blank line between paragraphs
% ----------------------------------------------------------------------------------------------------------------------------------
\setlength\parindent{0pt}
\usepackage{parskip}
% Add page headers and footers
% ----------------------------------------------------------------------------------------------------------------------------------
\usepackage{fancyhdr}
\fancyhead[LE,RO]{\slshape \rightmark}
\fancyhead[LO,RE]{\slshape \leftmark}
\fancypagestyle{plain}
{
\fancyhead{}
\lhead[]{TABLE OF CONTENTS}
}
\lfoot[]{{[pdf-title]}\\
Version {[version]}}
\cfoot[]{\ \\-\ \thepage\ -}
\rfoot[]{Crunchy Data Solutions, Inc.\\\today}
\pagestyle{fancy}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}
% ----------------------------------------------------------------------------------------------------------------------------------
% Begin document
% ----------------------------------------------------------------------------------------------------------------------------------
\begin{document}
% Create the title page
% ----------------------------------------------------------------------------------------------------------------------------------
\makeatletter
\begin{titlepage}
\begin{center}
{\large \ }\\[18ex]
{\huge \bfseries {[pdf-title]}}\\[1ex]
{\large \bfseries Version {[version]}}\\[4ex]
{\large {[pdf-subtitle]}}\\[12ex]
\includegraphics[width=6in]{{[logo]}}\\[12ex]
{\large Crunchy Data Solutions, Inc.}\\[1ex]
{\large \today}
\end{center}
\end{titlepage}
\makeatother
\thispagestyle{empty}
\newpage
% Generate TOC
% ----------------------------------------------------------------------------------------------------------------------------------
\setcounter{tocdepth}{3}
\topskip0in
\thispagestyle{plain}
\renewcommand\contentsname{Table of Contents}
\tableofcontents
% ----------------------------------------------------------------------------------------------------------------------------------
% Content
% ----------------------------------------------------------------------------------------------------------------------------------

View File

@ -8,6 +8,9 @@
<changelog>
<changelog-release date="XXXX-XX-XX" version="0.90" title="UNDER DEVELOPMENT">
<release-feature-bullet-list>
<release-feature>
<text>Added documentation in the user guide for delta restores, expiration, dedicated backup hosts, starting and stopping <backrest/>, and replication.</text>
</release-feature>
<release-feature>
<text>Fixed an issue where the <cmd>start</cmd>/<cmd>stop</cmd> commands required the <setting>--config</setting> option.</text>
</release-feature>
@ -143,7 +146,7 @@
<text>Added vagrant test configurations for Ubuntu 14.04 and CentOS 7.</text>
</release-feature>
<release-feature>
<text>Split most of <file>README.md</file> out into <file>USERGUIDE.md</file> and <file>CHANGELOG.md</file> because it was becoming unwieldy. Changed most references to "database" in the user guide to "database cluster" for clarity.</text>
<text>Split most of <file>README.md</file> out into <file>USERGUIDE.md</file> and <file>CHANGELOG.md</file> because it was becoming unwieldy. Changed most references to <quote>database</quote> in the user guide to <quote>database cluster</quote> for clarity.</text>
</release-feature>
</release-feature-bullet-list>
</changelog-release>

View File

@ -50,45 +50,65 @@
<!ATTLIST config-key name CDATA #REQUIRED>
<!ELEMENT execute-list (title, execute+)>
<!ELEMENT execute (exe-cmd, exe-user?, exe-var?, exe-retry?, exe-output?, exe-no-show?, exe-highlight?, exe-skip?,
<!ATTLIST execute-list host CDATA #REQUIRED>
<!ATTLIST execute-list keyword CDATA "">
<!ELEMENT execute (exe-cmd, exe-user?, exe-var?, exe-retry?, exe-output?, (exe-highlight-type?, exe-highlight)?,
exe-err-expect?, exe-err-suppress?, exe-err-suppress-stderr?)>
<!ATTLIST execute keyword CDATA "">
<!ATTLIST execute user CDATA "">
<!ATTLIST execute filter CDATA "">
<!ATTLIST execute filter-context CDATA "">
<!ATTLIST execute skip CDATA "">
<!ATTLIST execute show CDATA "">
<!ATTLIST execute output CDATA "">
<!ATTLIST execute err-suppress CDATA "">
<!ATTLIST execute err-expect CDATA "">
<!ATTLIST execute retry CDATA "">
<!ATTLIST execute variable-key CDATA "">
<!ELEMENT exe-cmd (#PCDATA)>
<!ELEMENT exe-user (#PCDATA)>
<!ELEMENT exe-highlight (#PCDATA)>
<!ELEMENT exe-var (#PCDATA)>
<!ELEMENT exe-output EMPTY>
<!ELEMENT exe-skip EMPTY>
<!ELEMENT exe-no-show EMPTY>
<!ELEMENT exe-retry EMPTY>
<!ELEMENT exe-err-expect (#PCDATA)>
<!ELEMENT exe-err-suppress EMPTY>
<!ELEMENT exe-err-suppress-stderr EMPTY>
<!ELEMENT exe-highlight-type (#PCDATA)>
<!ELEMENT cleanup (execute+)>
<!ELEMENT variable-list (variable+)>
<!ELEMENT variable (variable-name, variable-value)>
<!ELEMENT variable-name (#PCDATA)>
<!ELEMENT variable-value (#PCDATA)>
<!ELEMENT variable (#PCDATA)>
<!ATTLIST variable key CDATA #REQUIRED>
<!ATTLIST variable keyword CDATA "default">
<!ATTLIST variable eval CDATA "n">
<!ELEMENT backrest-config (title, backrest-config-option+)>
<!ATTLIST backrest-config host CDATA #REQUIRED>
<!ATTLIST backrest-config owner CDATA "">
<!ATTLIST backrest-config show CDATA "">
<!ATTLIST backrest-config keyword CDATA "">
<!ATTLIST backrest-config reset CDATA "">
<!ATTLIST backrest-config file CDATA #REQUIRED>
<!ELEMENT backrest-config-option (backrest-config-option-section?, backrest-config-option-key, backrest-config-option-value?)>
<!ELEMENT backrest-config-option-section (#PCDATA)>
<!ELEMENT backrest-config-option-key (#PCDATA)>
<!ELEMENT backrest-config-option-value (#PCDATA)>
<!ELEMENT backrest-config-option (#PCDATA)>
<!ATTLIST backrest-config-option keyword CDATA "">
<!ATTLIST backrest-config-option section CDATA #REQUIRED>
<!ATTLIST backrest-config-option key CDATA #REQUIRED>
<!ATTLIST backrest-config-option remove CDATA "n">
<!ELEMENT postgres-config (title, postgres-config-option+)>
<!ATTLIST postgres-config host CDATA #REQUIRED>
<!ATTLIST postgres-config file CDATA #REQUIRED>
<!ATTLIST postgres-config keyword CDATA "">
<!ATTLIST postgres-config show CDATA "">
<!ELEMENT postgres-config-option (#PCDATA)>
<!ATTLIST postgres-config-option keyword CDATA "">
<!ATTLIST postgres-config-option key CDATA #REQUIRED>
<!ELEMENT option-description EMPTY>
<!ATTLIST option-description key CDATA #REQUIRED>
<!ELEMENT section (title,((p|execute-list|backrest-config|postgres-config|option-description)+|(p*, section+)|p*))>
<!ELEMENT section (title,
((p|table|host-add|execute-list|backrest-config|postgres-config|option-description|code-block)+|
(p*, section+)|p*))>
<!ATTLIST section id CDATA #REQUIRED>
<!ELEMENT title (#PCDATA|b|i|bi|ul|ol|id|code|code-block|file|path|cmd|param|setting|exe|backrest|postgres|br-option|br-setting|
<!ATTLIST section keyword CDATA "">
<!ATTLIST section depend CDATA "">
<!ELEMENT title (#PCDATA|b|i|bi|ul|ol|id|code|file|path|cmd|param|setting|exe|backrest|postgres|br-option|br-setting|
pg-option|pg-setting|link|user)*>
<!ELEMENT default (#PCDATA)>
@ -115,26 +135,54 @@
<!ELEMENT support (text)>
<!ATTLIST support title CDATA #REQUIRED>
<!ELEMENT summary (#PCDATA|b|i|bi|ul|ol|id|code|code-block|file|path|cmd|param|setting|exe|backrest|postgres|br-option|br-setting|
pg-option|pg-setting|link|user)*>
<!ELEMENT p (#PCDATA|b|i|bi|ul|ol|id|code|code-block|file|path|cmd|param|setting|exe|backrest|postgres|br-option|br-setting|
<!-- Host elements -->
<!ELEMENT host-add (execute*)>
<!ATTLIST host-add name CDATA #REQUIRED>
<!ATTLIST host-add user CDATA #REQUIRED>
<!ATTLIST host-add image CDATA #REQUIRED>
<!ATTLIST host-add os CDATA "">
<!ATTLIST host-add mount CDATA "">
<!-- Table elements -->
<!ELEMENT table (title?, table-header, table-data)>
<!ELEMENT table-header (table-column+)>
<!ATTLIST table-header width CDATA "">
<!ELEMENT table-column (#PCDATA|quote|b|i|id|code|host|file|path|cmd|param|setting|exe|backrest|postgres|br-option|
br-setting|pg-option|pg-setting|link|user)*>
<!ATTLIST table-column align CDATA "">
<!ATTLIST table-column fill CDATA "">
<!ELEMENT table-data (table-row+)>
<!ELEMENT table-row (table-cell+)>
<!ELEMENT table-cell (#PCDATA|quote|b|i|ul|ol|id|code|code-block|host|file|path|cmd|param|setting|exe|backrest|postgres|br-option|
br-setting|pg-option|pg-setting|link|user)*>
<!-- Formatted elements -->
<!ELEMENT summary (#PCDATA|quote|b|i|ul|ol|id|code|code-block|host|file|path|cmd|param|setting|exe|backrest|postgres|br-option|
br-setting|pg-option|pg-setting|link|user)*>
<!ELEMENT p (#PCDATA|quote|b|i|ul|ol|id|code|code-block|host|file|path|cmd|param|setting|exe|backrest|postgres|br-option|br-setting|
pg-option|pg-setting|link|user)*>
<!ELEMENT text (#PCDATA|b|i|bi|ul|ol|id|code|code-block|file|path|cmd|param|setting|exe|backrest|postgres|br-option|br-setting|
pg-option|pg-setting|link|user)*>
<!ATTLIST p keyword CDATA "">
<!ELEMENT text (#PCDATA|quote|b|i|ul|ol|id|code|code-block|host|file|path|cmd|param|setting|exe|backrest|postgres|br-option|
br-setting|pg-option|pg-setting|link|user)*>
<!ELEMENT i (#PCDATA)>
<!ELEMENT b (#PCDATA)>
<!ELEMENT bi (#PCDATA)>
<!ELEMENT ul (li+)>
<!ELEMENT ol (li+)>
<!ELEMENT li (#PCDATA|b|i|bi|ul|ol|id|code|code-block|file|path|cmd|param|setting|exe|backrest|postgres|br-option|br-setting|
pg-option|pg-setting|link|user)*>
<!ELEMENT li (#PCDATA|quote|b|i|ul|ol|id|code|code-block|host|file|path|cmd|param|setting|exe|backrest|postgres|br-option|
br-setting|pg-option|pg-setting|link|user)*>
<!ELEMENT id (#PCDATA)>
<!ELEMENT code (#PCDATA)>
<!ELEMENT code-block (#PCDATA|exe)*>
<!ATTLIST code-block title CDATA #REQUIRED>
<!ELEMENT host (#PCDATA)>
<!ELEMENT file (#PCDATA)>
<!ELEMENT path (#PCDATA)>
<!ELEMENT cmd (#PCDATA)>
<!ELEMENT user (#PCDATA)>
<!ELEMENT quote (#PCDATA)>
<!ELEMENT param (#PCDATA)>
<!ELEMENT setting (#PCDATA)>
<!ELEMENT br-option (#PCDATA)>

21
doc/xml/dtd/manifest.dtd Normal file
View File

@ -0,0 +1,21 @@
<!ELEMENT doc (variable-list, source-list, render-list)>
<!ELEMENT variable-list (variable+)>
<!ELEMENT variable (#PCDATA)>
<!ATTLIST variable key CDATA #REQUIRED>
<!ATTLIST variable eval CDATA "n">
<!ATTLIST variable keyword CDATA "default">
<!ELEMENT source-list (source+)>
<!ELEMENT source EMPTY>
<!ATTLIST source key CDATA #REQUIRED>
<!ATTLIST source type CDATA "">
<!ELEMENT render-list (render+)>
<!ELEMENT render (render-source+)>
<!ATTLIST render type CDATA #REQUIRED>
<!ATTLIST render file CDATA "">
<!ELEMENT render-source EMPTY>
<!ATTLIST render-source key CDATA #REQUIRED>
<!ATTLIST render-source source CDATA "">
<!ATTLIST render-source menu CDATA "">

View File

@ -3,54 +3,21 @@
<doc subtitle="Reliable {[postgres]} Backup &amp; Restore" toc="n">
<variable-list>
<!-- Variables used by the rest of the script -->
<variable>
<variable-name>github-url-base</variable-name>
<variable-value>https://github.com/pgmasters/backrest</variable-value>
</variable>
<variable>
<variable-name>github-url-master</variable-name>
<variable-value>{[github-url-base]}/blob/master</variable-value>
</variable>
<variable>
<variable-name>github-url-issues</variable-name>
<variable-value>{[github-url-base]}/issues</variable-value>
</variable>
<variable>
<variable-name>github-url-change-log</variable-name>
<variable-value>{[github-url-master]}/CHANGELOG.md</variable-value>
</variable>
<variable>
<variable-name>github-url-license</variable-name>
<variable-value>{[github-url-master]}/LICENSE</variable-value>
</variable>
<variable>
<variable-name>backrest-url-base</variable-name>
<variable-value>http://www.pgbackrest.org</variable-value>
</variable>
<variable>
<variable-name>backrest-page-user-guide</variable-name>
<variable-value>user-guide.html</variable-value>
</variable>
<variable>
<variable-name>backrest-page-configuration</variable-name>
<variable-value>configuration.html</variable-value>
</variable>
<variable>
<variable-name>backrest-page-command</variable-name>
<variable-value>command.html</variable-value>
</variable>
<variable>
<variable-name>crunchy-url-base</variable-name>
<variable-value>http://www.crunchydatasolutions.com</variable-value>
</variable>
<variable>
<variable-name>crunchy-url-cbm</variable-name>
<variable-value>{[crunchy-url-base]}/crunchy-backup-manager</variable-value>
</variable>
<variable>
<variable-name>resonate-url-base</variable-name>
<variable-value>http://www.resonate.com</variable-value>
</variable>
<variable key="github-url-base">https://github.com/pgmasters/backrest</variable>
<variable key="github-url-master">{[github-url-base]}/blob/master</variable>
<variable key="github-url-issues">{[github-url-base]}/issues</variable>
<variable key="github-url-change-log">{[github-url-master]}/CHANGELOG.md</variable>
<variable key="github-url-license">{[github-url-master]}/LICENSE</variable>
<variable key="backrest-url-base">http://www.pgbackrest.org</variable>
<variable key="backrest-page-user-guide">user-guide.html</variable>
<variable key="backrest-page-configuration">configuration.html</variable>
<variable key="backrest-page-command">command.html</variable>
<variable key="crunchy-url-base">http://www.crunchydatasolutions.com</variable>
<variable key="crunchy-url-cbm">{[crunchy-url-base]}/crunchy-backup-manager</variable>
<variable key="resonate-url-base">http://www.resonate.com</variable>
</variable-list>
<section id="introduction">

View File

@ -1,162 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE doc SYSTEM "doc.dtd">
<doc title="User Guide">
<!-- <intro>
<text></text>
</intro> -->
<!-- <install title="Installation">
<text><backrest/> is written entirely in Perl. Some additional modules will need to be installed depending on the OS.</text>
<install-system-list>
<install-system title="Ubuntu 12.04/14.04 Setup">
<text>* Install required Perl modules:
<code-block>
apt-get install libdbd-pg-perl
</code-block></text>
</install-system>
<install-system title="CentOS 6 Setup">
<text>* Install Perl and required modules:
<code-block>
yum install perl perl-Time-HiRes perl-parent perl-JSON perl-Digest-SHA perl-DBD-Pg
</code-block></text>
</install-system>
<install-system title="CentOS 7 Setup">
<text>* Install Perl and required modules:
<code-block>
yum install perl perl-Thread-Queue perl-JSON-PP perl-Digest-SHA perl-DBD-Pg
</code-block></text>
</install-system>
<install-system title="Software Installation">
<text><backrest/> can be installed by downloading the most recent release:
https://github.com/pgmasters/backrest/releases
<backrest/> can be installed anywhere but it's best (though not required) to install it in the same location on all systems.</text>
</install-system>
<install-system title="Regression Test Setup">
<text>* Create the backrest user
The backrest user must be created on the same system and in the same group as the user you will use for testing (which can be any user you prefer). For example:
<code-block>
adduser -g &lt;test-user-group&gt; backrest
</code-block>
* Setup password-less SSH login between the test user and the backrest user
The test user should be able to `ssh backrest@127.0.0.1` and the backrest user should be able to `ssh &lt;testuser&gt;@127.0.0.1` without requiring any passwords. This article (http://archive.oreilly.com/pub/h/66) has details on how to accomplish this. Do the logons both ways at the command line before running regression tests.
* Give group read and execute permissions to <path>~/backrest/test</path>:
Usually this can be accomplished by running the following as the test user:
<code-block>
chmod 750 ~
</code-block>
* Running regression:
Running the full regression suite is generally not necessary. Run the following first:
<code-block>
./test.pl {[dash]}-module=backup {[dash]}-test=full {[dash]}-db-version=all {[dash]}-thread-max=&lt;# threads&gt;
</code-block>
This will run full backup/restore regression with a variety of options on all installed versions of <postgres/>. If you are only interested in one version then modify the <setting>db-version</setting> setting to X.X (e.g. 9.4). <setting>{[dash]}-thread-max</setting> can be omitted if you are running single-threaded.
If there are errors in this test then run full regression to help isolate problems:
<code-block>
./test.pl {[dash]}-db-version=all {[dash]}-thread-max=&lt;# threads&gt;
</code-block>
Report regression test failures at https://github.com/pgmasters/backrest/issues.</text>
</install-system>
</install-system-list>
</install> -->
<!-- CONFIG -->
<config title="Configuration Reference">
<text><backrest/> can be used entirely with command-line parameters but a configuration file is more practical for installations that are complex or set a lot of options. The default location for the configuration file is <file>/etc/pg_backrest.conf</file>.</text>
<!--
<config-example-list title="Examples">
<config-example title="Confguring Postgres for Archiving">
<text>Modify the following settings in <file>postgresql.conf</file>:
<code-block>
wal_level = archive
archive_mode = on
archive_command = '/path/to/backrest/bin/<exe/> -stanza=db archive-push %p'
</code-block>
Replace the path with the actual location where <backrest/> was installed. The stanza parameter should be changed to the actual stanza name for your database cluster.</text>
</config-example>
<config-example title="Minimal Configuration">
<text>The absolute minimum required to run <backrest/> (if all defaults are accepted) is the database cluster path.
<file>/etc/pg_backrest.conf</file>:
<code-block>
[main]
db-path=/data/db
</code-block>
The <setting>db-path</setting> option could also be provided on the command line, but it's best to use a configuration file as options tend to pile up quickly.</text>
</config-example>
<config-example title="Simple Single Host Configuration">
<text>This configuration is appropriate for a small installation where backups are being made locally or to a remote file system that is mounted locally. A number of additional options are set:
<ul>
<li><setting>db-port</setting> - Custom port for <postgres/>.</li>
<li><setting>compress</setting> - Disable compression (handy if the file system is already compressed).</li>
<li><setting>repo-path</setting> - Path to the <backrest/> repository where backups and WAL archive are stored.</li>
<li><setting>log-level-file</setting> - Set the file log level to debug (Lots of extra info if something is not working as expected).</li>
<li><setting>hardlink</setting> - Create hardlinks between backups (but never between full backups).</li>
<li><setting>thread-max</setting> - Use 2 threads for backup/restore operations.</li>
</ul>
<file>/etc/pg_backrest.conf</file>:
<code-block>
[global:general]
compress=n
repo-path=/path/to/db/repo
[global:log]
log-level-file=debug
[global:backup]
hardlink=y
thread-max=2
[main]
db-path=/data/db
db-port=5555
</code-block></text>
</config-example>
<config-example title="Simple Multiple Host Configuration">
<text>This configuration is appropriate for a small installation where backups are being made remotely. Make sure that postgres@db-host has trusted ssh to backrest@backup-host and vice versa. This configuration assumes that you have <exe/> in the same path on both servers.
<file>/etc/pg_backrest.conf</file> on the db host:
<code-block>
[global:general]
repo-path=/path/to/db/repo
repo-remote-path=/path/to/backup/repo
[global:backup]
backup-host=backup.mydomain.com
backup-user=backrest
[global:archive]
archive-async=y
[main]
db-path=/data/db
</code-block>
<file>/etc/pg_backrest.conf</file> on the backup host:
<code-block>
[global:general]
repo-path=/path/to/backup/repo
[main]
db-host=db.mydomain.com
db-path=/data/db
db-user=postgres
</code-block></text>
</config-example>
</config-example-list> -->
<config-section-list title="Settings">
<!-- CONFIG - COMMAND SECTION -->
@ -171,7 +18,7 @@
<text>Required only if the path to <exe/> is different on the local and remote systems. If not defined, the remote exe path will be set the same as the local exe path.</text>
<default>same as local</default>
<example>/usr/lib/backrest/bin/pg_backrest_remote.pl</example>
<example>/usr/lib/backrest/bin/pg_backrest</example>
</config-key>
</config-key-list>
</config-section>
@ -583,6 +430,7 @@
</config-section-list>
</config>
<!-- COMMAND -->
<operation title="Command Reference">
<text>Commands are used to execute the various <backrest/> functions. Here the command options are listed exhaustively, that is, each option applicable to a command is listed with that command even if it applies to one or more other commands. This includes all the options that may also configured in <file>pg_backrest.conf</file>.</text>
@ -657,8 +505,8 @@
<command-example-list>
<command-example title="Full Backup">
<text><code-block>
<exe/> --stanza=db --type=full backup
<text><code-block title="">
{[backrest-exe]} --stanza=db --type=full backup
</code-block>
Run a <id>full</id> backup on the <id>db</id> stanza. <br-option>--type</br-option> can also be set to <id>incr</id> or <id>diff</id> for incremental or differential backups. However, if no <id>full</id> backup exists then a <id>full</id> backup will be forced even if <id>incr</id> or <id>diff</id> is requested.</text>
</command-example>
@ -673,8 +521,8 @@
<command-example-list>
<command-example>
<text><code-block>
<exe/> --stanza=db archive-push %p
<text><code-block title="">
{[backrest-exe]} --stanza=db archive-push %p
</code-block>
Accepts a WAL segment from <postgres/> and archives it in the repository defined by <setting>repo-path</setting>. <id>%p</id> is how <postgres/> specifies the location of the WAL segment to be archived.</text>
</command-example>
@ -689,8 +537,8 @@
<command-example-list>
<command-example>
<text><code-block>
<exe/> --stanza=db archive-get %f %p
<text><code-block title="">
{[backrest-exe]} --stanza=db archive-get %f %p
</code-block>
Retrieves a WAL segment from the repository. This command is used in <file>recovery.conf</file> to restore a backup, perform PITR, or as an alternative to streaming for keeping a replica up to date. <id>%f</id> is how <postgres/> specifies the WAL segment it needs and <id>%p</id> is the location where it should be copied.</text>
</command-example>
@ -705,8 +553,8 @@
<command-example-list>
<command-example>
<text><code-block>
<exe/> --stanza=db expire
<text><code-block title="">
{[backrest-exe]} --stanza=db expire
</code-block>
Expire (rotate) any backups that exceed the defined retention. Expiration is run automatically after every successful backup, so there is no need to run this command separately unless you have reduced retention, usually to free up some space.</text>
</command-example>
@ -793,6 +641,7 @@
<summary>Recover along a timeline.</summary>
<text>See <setting>recovery_target_timeline</setting> in the <postgres/> docs for more information.</text>
<example>3</example>
</option>
@ -804,13 +653,16 @@
Note: The <setting>restore_command</setting> option will be automatically generated but can be overridden with this option. Be careful about specifying your own <setting>restore_command</setting> as <backrest/> is designed to handle this for you. Target Recovery options (recovery_target_name, recovery_target_time, etc.) are generated automatically by <backrest/> and should not be set with this option.
Recovery settings can also be set in the <setting>restore:recovery-option</setting> section of pg_backrest.conf. For example:
<code-block>
Recovery settings can also be set in the <setting>restore:recovery-option</setting> section of pg_backrest.conf.
<!-- For example:
<code-block title="">
[restore:recovery-option]
primary_conn_info=db.mydomain.com
standby_mode=on
</code-block>
</code-block> -->
Since <backrest/> does not start <postgres/> after writing the <file>recovery.conf</file> file, it is always possible to edit/check <file>recovery.conf</file> before manually restarting.</text>
<example>primary_conninfo=db.mydomain.com</example>
</option>
@ -821,14 +673,15 @@
<text>Moves a tablespace to a new location during the restore. This is useful when tablespace locations are not the same on a replica, or an upgraded system has different mount points.
Since <postgres/> 9.2 tablespace locations are not stored in pg_tablespace so moving tablespaces can be done with impunity. However, moving a tablespace to the <setting>data_directory</setting> is not recommended and may cause problems. For more information on moving tablespaces http://www.databasesoup.com/2013/11/moving-tablespaces.html is a good resource.</text>
<example>ts_01=/db/ts_01</example>
</option>
</option-list>
<command-example-list>
<command-example title="Restore Latest">
<text><code-block>
<exe/> --stanza=db --type=name --target=release restore
<text><code-block title="">
{[backrest-exe]} --stanza=db --type=name --target=release restore
</code-block>
Restores the latest database cluster backup and then recovers to the <id>release</id> restore point.</text>
</command-example>
@ -860,16 +713,16 @@
<command-example-list>
<command-example title="Information for a single stanza">
<text><code-block>
<exe/> --stanza=db --output=json info
<text><code-block title="">
{[backrest-exe]} --stanza=db --output=json info
</code-block>
Get information about backups in the <id>db</id> stanza.</text>
</command-example>
<command-example title="Information for all stanzas">
<text><code-block>
<exe/> --output=json info
<text><code-block title="">
{[backrest-exe]} --output=json info
</code-block>
Get information about backups for all stanzas in the repository.</text>
@ -885,16 +738,16 @@
<command-example-list>
<command-example title="Help for the backup command">
<text><code-block>
<exe/> help backup
<text><code-block title="">
{[backrest-exe]} help backup
</code-block>
Get help for the backup command.</text>
</command-example>
<command-example title="Help for backup command, --force option">
<text><code-block>
<exe/> help backup force
<text><code-block title="">
{[backrest-exe]} help backup force
</code-block>
Get help for the force option of the backup command.</text>
@ -910,8 +763,8 @@
<command-example-list>
<command-example title="Start processes for stanza main">
<text><code-block>
<exe/> --stanza=main start
<text><code-block title="">
{[backrest-exe]} --stanza=main start
</code-block>
Allows <backrest/> processes to run for the <id>main</id> stanza.</text>
@ -940,8 +793,8 @@
<command-example-list>
<command-example title="Stop processes for all stanzas">
<text><code-block>
<exe/> stop
<text><code-block title="">
{[backrest-exe]} stop
</code-block>
Stop new <backrest/> processes for all stanzas but allow any current process to complete.</text>
@ -957,8 +810,8 @@
<command-example-list>
<command-example title="Get version">
<text><code-block>
<exe/> version
<text><code-block title="">
{[backrest-exe]} version
</code-block>
Get <backrest/> version.</text>

File diff suppressed because it is too large Load Diff

View File

@ -872,12 +872,7 @@ my $oConfigHelpData =
"this for you. Target Recovery options (recovery_target_name, recovery_target_time, etc.) are " .
"generated automatically by pgBackRest and should not be set with this option.\n" .
"\n" .
"Recovery settings can also be set in the restore:recovery-option section of pg_backrest.conf. For " .
"example:\n" .
"\n" .
"[restore:recovery-option]\n" .
"primary_conn_info=db.mydomain.com\n" .
"standby_mode=on\n" .
"Recovery settings can also be set in the restore:recovery-option section of pg_backrest.conf.\n" .
"\n" .
"Since pgBackRest does not start PostgreSQL after writing the recovery.conf file, it is always possible " .
"to edit/check recovery.conf before manually restarting."

View File

@ -0,0 +1,224 @@
####################################################################################################################################
# HostTest.pm - Encapsulate a docker host for testing
####################################################################################################################################
package BackRestTest::Common::HostTest;
####################################################################################################################################
# Perl includes
####################################################################################################################################
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 lib dirname($0) . '/../lib';
use BackRest::Common::Log;
use BackRest::Common::String;
use BackRestTest::Common::ExecuteTest;
####################################################################################################################################
# Operation constants
####################################################################################################################################
use constant OP_HOST_TEST => 'LogTest';
use constant OP_HOST_TEST_COPY_FROM => OP_HOST_TEST . "->copyFrom";
use constant OP_HOST_TEST_COPY_TO => OP_HOST_TEST . "->copyTo";
use constant OP_HOST_TEST_EXECUTE => OP_HOST_TEST . "->execute";
use constant OP_HOST_TEST_EXECUTE_SIMPLE => OP_HOST_TEST . "->executeSimple";
use constant OP_HOST_TEST_NEW => OP_HOST_TEST . "->new";
####################################################################################################################################
# new
####################################################################################################################################
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->{strName},
$self->{strImage},
$self->{strUser},
$self->{strOS},
$self->{strMount}
) =
logDebugParam
(
OP_HOST_TEST_NEW, \@_,
{name => 'strName', trace => true},
{name => 'strImage', trace => true},
{name => 'strUser', trace => true},
{name => 'strOS', trace => true},
{name => 'strMount', trace => true}
);
executeTest("docker kill $self->{strName}", {bSuppressError => true});
executeTest("docker rm $self->{strName}", {bSuppressError => true});
executeTest("rm -rf ~/data/$self->{strName}");
executeTest("mkdir -p ~/data/$self->{strName}/etc");
executeTest("docker run -itd -h $self->{strName} --name=$self->{strName} " .
(defined($self->{strMount}) ? "-v $self->{strMount} " : '') .
"$self->{strImage}");
$self->{strIP} = trim(executeTest("docker inspect --format '\{\{ .NetworkSettings.IPAddress \}\}' $self->{strName}"));
$self->{bActive} = true;
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self, trace => true}
);
}
####################################################################################################################################
# execute
####################################################################################################################################
sub execute
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strCommand,
$oParam,
$strUser
) =
logDebugParam
(
OP_HOST_TEST_EXECUTE, \@_,
{name => 'strCommand'},
{name => 'oParam', required=> false},
{name => 'strUser', required => false}
);
# Set the user
if (!defined($strUser))
{
$strUser = $self->{strUser};
}
my $oExec = new BackRestTest::Common::ExecuteTest(
'docker exec ' . ($strUser eq 'root' ? "-u ${strUser} " : '') . "$self->{strName} ${strCommand}" , $oParam);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oExec', value => $oExec, trace => true}
);
}
####################################################################################################################################
# executeSimple
####################################################################################################################################
sub executeSimple
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strCommand,
$oParam,
$strUser
) =
logDebugParam
(
OP_HOST_TEST_EXECUTE_SIMPLE, \@_,
{name => 'strCommand', trace => true},
{name => 'oParam', required=> false, trace => true},
{name => 'strUser', required => false, trace => true}
);
my $oExec = $self->execute($strCommand, $oParam, $strUser);
$oExec->begin();
$oExec->end();
return $oExec->{strOutLog};
}
####################################################################################################################################
# copyTo
####################################################################################################################################
sub copyTo
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strSource,
$strDestination,
$strOwner,
$strMode
) =
logDebugParam
(
OP_HOST_TEST_COPY_TO, \@_,
{name => 'strSource'},
{name => 'strDestination'},
{name => 'strOwner', required => false},
{name => 'strMode', required => false}
);
executeTest("docker cp ${strSource} $self->{strName}:${strDestination}");
if (defined($strOwner))
{
$self->executeSimple("chown ${strOwner} ${strDestination}", undef, 'root');
}
if (defined($strMode))
{
$self->executeSimple("chmod ${strMode} ${strDestination}", undef, 'root');
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# copyFrom
####################################################################################################################################
sub copyFrom
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strSource,
$strDestination
) =
logDebugParam
(
OP_HOST_TEST_COPY_FROM, \@_,
{name => 'strSource'},
{name => 'strDestination'}
);
executeTest("docker cp $self->{strName}:${strSource} ${strDestination}");
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
1;

55
test/vm/Vagrantfile vendored
View File

@ -36,7 +36,7 @@ Vagrant.configure(2) do |config|
/backrest/test/vm/ssh/setup-cm.sh
# Install required Perl modules
apt-get install libdbi-perl libdbd-pg-perl
apt-get install -y libdbi-perl libdbd-pg-perl
# Install Perl modules required for building the docs
apt-get install -y libxml-checker-perl libxml-writer-perl
@ -82,6 +82,59 @@ Vagrant.configure(2) do |config|
SHELL
end
config.vm.define "u14doc" do |u14doc|
u14doc.vm.box = "boxcutter/ubuntu1404"
u14doc.vm.provider :virtualbox do |vb|
vb.name = "backrest-doc-test-ubuntu-14.04"
end
# Provision the VM
u14doc.vm.provision "shell", inline: <<-SHELL
# Install docker
apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
echo 'deb https://apt.dockerproject.org/repo ubuntu-trusty main' > /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo apt-get -y install linux-image-extra-$(uname -r)
sudo apt-get -y install docker-engine
sudo usermod -aG docker vagrant
# This requires a reboot - maybe don't do it - see how it goes first
# sed -i 's/^GRUB_CMDLINE_LINUX\=.*$/GRUB_CMDLINE_LINUX\=\"cgroup_enable=memory swapaccount=1\"/' /etc/default/grub
# Install Perl modules
apt-get install -y libxml-checker-perl ghostscript
# Install texlive
mkdir /root/texlive
wget -q -O - http://mirror.hmc.edu/ctan/systems/texlive/tlnet/install-tl-unx.tar.gz | tar zxv -C /root//texlive --strip-components=1
echo "collection-basic 1" >> /root/texlive/texlive.profile
echo "collection-latex 1" >> /root/texlive/texlive.profile
/root/texlive/install-tl -profile=/root/texlive/texlive.profile
echo 'PATH=/usr/local/texlive/2015/bin/x86_64-linux:$PATH' >> /etc/profile
echo 'export PATH' >> /etc/profile
/usr/local/texlive/2015/bin/x86_64-linux/tlmgr install caption xcolor listings parskip helvetic ltablex titlesec \
epstopdf courier
# Build images
docker build -f /backrest/test/vm/docker/u14-base -t vagrant/u14-base /backrest/test/vm
docker build -f /backrest/test/vm/docker/u14-db -t vagrant/u14-db /backrest/test/vm
docker build -f /backrest/test/vm/docker/u14-backup -t vagrant/u14-backup /backrest/test/vm
docker build -f /backrest/test/vm/docker/co6-base -t vagrant/co6-base /backrest/test/vm
docker build -f /backrest/test/vm/docker/co6-db -t vagrant/co6-db /backrest/test/vm
docker build -f /backrest/test/vm/docker/co6-backup -t vagrant/co6-backup /backrest/test/vm
# Sample docker commands
# docker inspect --format '{{ .NetworkSettings.IPAddress }}' db-master
# docker run -itd --name=db-master -v /backrest:/backrest vagrant/u14-db
# docker run -itd --name=backup -v /backrest:/backrest vagrant/u14-backup
# docker exec -it db-master bash
SHELL
end
config.vm.define "co6" do |co6|
co6.vm.box = "boxcutter/centos67"

19
test/vm/docker/co6-backup Normal file
View File

@ -0,0 +1,19 @@
# CentOS 6 Backup Container
FROM vagrant/co6-base
# Create backrest user
RUN adduser -gpostgres -u5002 -n backrest
# Setup SSH
RUN mkdir /home/backrest/.ssh
RUN cp -p /root/resource/.ssh/* /home/backrest/.ssh
RUN chown -R backrest:postgres /home/backrest
RUN chmod 700 /home/backrest/.ssh
# Setup repository
RUN mkdir /var/lib/backrest
RUN chown -R backrest:postgres /var/lib/backrest
RUN chmod 750 /var/lib/backrest
# Install Perl packages
RUN yum -y install perl perl-Time-HiRes perl-parent perl-JSON perl-Digest-SHA perl-DBD-Pg

31
test/vm/docker/co6-base Normal file
View File

@ -0,0 +1,31 @@
# CentOS 6 Base Container
FROM centos:6.7
# Install SSH
RUN yum -y install openssh-server openssh-clients
# Create postgres user and group
RUN groupadd -g5000 postgres
RUN adduser -gpostgres -u5000 -n postgres
# Add Postgres packages
RUN rpm -ivh http://yum.postgresql.org/9.4/redhat/rhel-6-x86_64/pgdg-centos94-9.4-1.noarch.rpm
# Create vagrant user
RUN groupadd -g5001 admin
RUN adduser -gadmin -u5001 -n vagrant
# Install sudo and add admin to sudoers
RUN yum -y install sudo
RUN echo '%admin ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/vagrant
RUN sed -i 's/^Defaults requiretty$/\# Defaults requiretty/' /etc/sudoers
# Copy trusted SSH setup
COPY ssh/config /root/resource/.ssh/config
COPY ssh/id_rsa /root/resource/.ssh/id_rsa
COPY ssh/id_rsa.pub /root/resource/.ssh/authorized_keys
RUN chmod 700 /root/resource/.ssh
RUN chmod 600 /root/resource/.ssh/*
# Start SSH when container starts
ENTRYPOINT service sshd restart && bash

16
test/vm/docker/co6-db Normal file
View File

@ -0,0 +1,16 @@
# CentOS 6 Database Container
FROM vagrant/co6-base
# Install Postgres
RUN yum -y install postgresql94-server
# Create pg_backrest.conf
RUN touch /etc/pg_backrest.conf
RUN chmod 640 /etc/pg_backrest.conf
RUN chown postgres:postgres /etc/pg_backrest.conf
# Setup SSH
RUN mkdir /home/postgres/.ssh
RUN cp /root/resource/.ssh/* /home/postgres/.ssh
RUN chown -R postgres:postgres /home/postgres/.ssh
RUN chmod 700 /home/postgres/.ssh

19
test/vm/docker/u14-backup Normal file
View File

@ -0,0 +1,19 @@
# Ubuntu 14.04 Backup Container
FROM vagrant/u14-base
# Create backrest user
RUN adduser --ingroup=postgres --disabled-password --gecos "" backrest
# Setup SSH
RUN mkdir -p /home/backrest/.ssh
RUN cp /root/resource/.ssh/* /home/backrest/.ssh
RUN chown -R backrest:postgres /home/backrest/.ssh
RUN chmod 700 /home/backrest/.ssh
# Setup repository
RUN mkdir /var/lib/backrest
RUN chown backrest:postgres /var/lib/backrest
RUN chmod 750 /var/lib/backrest
# Install Perl packages
RUN apt-get -y install libdbd-pg-perl libdbi-perl libnet-daemon-perl libplrpc-perl

41
test/vm/docker/u14-base Normal file
View File

@ -0,0 +1,41 @@
# Ubuntu 14.04 Base Container
FROM ubuntu:14.04
# Get packages that make up apt-get download
# apt-get -dy install ??? --print-uris -qq | sed -n "s/'\([^ ]\+\)' \([^ ]\+\) \([^ ]\+\) MD5Sum:\([^ ]\+\)/wget -c \1/p"
# Install SSH
RUN apt-get -y install openssh-server
# Create postgres user and group
RUN groupadd -g5000 postgres
RUN adduser --ingroup=postgres --disabled-password --gecos "" postgres
# Add Postgres packages
RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main 9.5' >> /etc/apt/sources.list.d/pgdg.list
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
RUN sudo apt-get update
# Create vagrant user
RUN addgroup admin
RUN adduser --ingroup=admin --disabled-password --gecos "" vagrant
RUN sed -i 's/^\%admin.*$/\%admin ALL\=\(ALL\) NOPASSWD\: ALL/' /etc/sudoers
# Suppress dpkg interactive output
RUN rm /etc/apt/apt.conf.d/70debconf
# Download Perl packages to be installed later
RUN apt-get -dy install libdbd-pg-perl libdbi-perl libnet-daemon-perl libplrpc-perl libpq5
# Download Postgres packages to be installed later
RUN apt-get -dy install postgresql-9.4
# Copy trusted SSH setup
COPY ssh/config /root/resource/.ssh/config
COPY ssh/id_rsa /root/resource/.ssh/id_rsa
COPY ssh/id_rsa.pub /root/resource/.ssh/authorized_keys
RUN chmod 700 /root/resource/.ssh
RUN chmod 600 /root/resource/.ssh/*
# Start SSH when container starts
ENTRYPOINT service ssh restart && bash

17
test/vm/docker/u14-db Normal file
View File

@ -0,0 +1,17 @@
# Ubuntu 14.04 Database Container
FROM vagrant/u14-base
# Install Postgres
RUN apt-get install -y postgresql-9.4
RUN pg_dropcluster --stop 9.4 main
# Create pg_backrest.conf
RUN touch /etc/pg_backrest.conf
RUN chmod 640 /etc/pg_backrest.conf
RUN chown postgres:postgres /etc/pg_backrest.conf
# Setup SSH
RUN mkdir /home/postgres/.ssh
RUN cp /root/resource/.ssh/* /home/postgres/.ssh
RUN chown -R postgres:postgres /home/postgres/.ssh
RUN chmod 700 /home/postgres/.ssh

View File

@ -1,2 +1,3 @@
Host *
StrictHostKeyChecking no
LogLevel quiet