1# -*- coding: utf-8 -*- 2# © Copyright EnterpriseDB UK Limited 2011-2021 3# 4# This file is part of Barman. 5# 6# Barman is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10# 11# Barman is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with Barman. If not, see <http://www.gnu.org/licenses/>. 18 19""" 20This module implements the interface with the command line and the logger. 21""" 22 23import json 24import logging 25import os 26import sys 27from argparse import SUPPRESS, ArgumentTypeError, ArgumentParser, HelpFormatter 28 29if sys.version_info.major < 3: 30 from argparse import Action, _SubParsersAction, _ActionsContainer 31import argcomplete 32from collections import OrderedDict 33from contextlib import closing 34 35 36import barman.config 37import barman.diagnose 38from barman import output 39from barman.annotations import KeepManager 40from barman.config import RecoveryOptions 41from barman.exceptions import ( 42 BadXlogSegmentName, 43 RecoveryException, 44 SyncError, 45 WalArchiveContentError, 46) 47from barman.infofile import BackupInfo, WalFileInfo 48from barman.server import Server 49from barman.utils import ( 50 BarmanEncoder, 51 check_non_negative, 52 check_positive, 53 configure_logging, 54 drop_privileges, 55 force_str, 56 get_log_levels, 57 parse_log_level, 58) 59from barman.xlog import check_archive_usable 60 61_logger = logging.getLogger(__name__) 62 63 64# Support aliases for argparse in python2. 65# Derived from https://gist.github.com/sampsyo/471779 and based on the 66# initial patchset for CPython for supporting aliases in argparse. 67# Licensed under CC0 1.0 68if sys.version_info.major < 3: 69 70 class AliasedSubParsersAction(_SubParsersAction): 71 old_init = staticmethod(_ActionsContainer.__init__) 72 73 @staticmethod 74 def _containerInit( 75 self, description, prefix_chars, argument_default, conflict_handler 76 ): 77 AliasedSubParsersAction.old_init( 78 self, description, prefix_chars, argument_default, conflict_handler 79 ) 80 self.register("action", "parsers", AliasedSubParsersAction) 81 82 class _AliasedPseudoAction(Action): 83 def __init__(self, name, aliases, help): 84 dest = name 85 if aliases: 86 dest += " (%s)" % ",".join(aliases) 87 sup = super(AliasedSubParsersAction._AliasedPseudoAction, self) 88 sup.__init__(option_strings=[], dest=dest, help=help) 89 90 def add_parser(self, name, **kwargs): 91 aliases = kwargs.pop("aliases", []) 92 parser = super(AliasedSubParsersAction, self).add_parser(name, **kwargs) 93 94 # Make the aliases work. 95 for alias in aliases: 96 self._name_parser_map[alias] = parser 97 # Make the help text reflect them, first removing old help entry. 98 if "help" in kwargs: 99 help_text = kwargs.pop("help") 100 self._choices_actions.pop() 101 pseudo_action = self._AliasedPseudoAction(name, aliases, help_text) 102 self._choices_actions.append(pseudo_action) 103 104 return parser 105 106 # override argparse to register new subparser action by default 107 _ActionsContainer.__init__ = AliasedSubParsersAction._containerInit 108 109 110class OrderedHelpFormatter(HelpFormatter): 111 def _format_usage(self, usage, actions, groups, prefix): 112 for action in actions: 113 if not action.option_strings: 114 action.choices = OrderedDict(sorted(action.choices.items())) 115 return super(OrderedHelpFormatter, self)._format_usage( 116 usage, actions, groups, prefix 117 ) 118 119 120p = ArgumentParser( 121 epilog="Barman by EnterpriseDB (www.enterprisedb.com)", 122 formatter_class=OrderedHelpFormatter, 123) 124p.add_argument( 125 "-v", 126 "--version", 127 action="version", 128 version="%s\n\nBarman by EnterpriseDB (www.enterprisedb.com)" % barman.__version__, 129) 130p.add_argument( 131 "-c", 132 "--config", 133 help="uses a configuration file " 134 "(defaults: %s)" % ", ".join(barman.config.Config.CONFIG_FILES), 135 default=SUPPRESS, 136) 137p.add_argument( 138 "--color", 139 "--colour", 140 help="Whether to use colors in the output", 141 choices=["never", "always", "auto"], 142 default="auto", 143) 144p.add_argument( 145 "--log-level", 146 help="Override the default log level", 147 choices=list(get_log_levels()), 148 default=SUPPRESS, 149) 150p.add_argument("-q", "--quiet", help="be quiet", action="store_true") 151p.add_argument("-d", "--debug", help="debug output", action="store_true") 152p.add_argument( 153 "-f", 154 "--format", 155 help="output format", 156 choices=output.AVAILABLE_WRITERS.keys(), 157 default=output.DEFAULT_WRITER, 158) 159 160subparsers = p.add_subparsers(dest="command") 161 162 163def argument(*name_or_flags, **kwargs): 164 """Convenience function to properly format arguments to pass to the 165 command decorator. 166 """ 167 168 # Remove the completer keyword argument from the dictionary 169 completer = kwargs.pop("completer", None) 170 return (list(name_or_flags), completer, kwargs) 171 172 173def command(args=None, parent=subparsers, cmd_aliases=None): 174 """Decorator to define a new subcommand in a sanity-preserving way. 175 The function will be stored in the ``func`` variable when the parser 176 parses arguments so that it can be called directly like so:: 177 args = cli.parse_args() 178 args.func(args) 179 Usage example:: 180 @command([argument("-d", help="Enable debug mode", action="store_true")]) 181 def command(args): 182 print(args) 183 Then on the command line:: 184 $ python cli.py command -d 185 """ 186 187 if args is None: 188 args = [] 189 if cmd_aliases is None: 190 cmd_aliases = [] 191 192 def decorator(func): 193 parser = parent.add_parser( 194 func.__name__.replace("_", "-"), 195 description=func.__doc__, 196 help=func.__doc__, 197 aliases=cmd_aliases, 198 ) 199 parent._choices_actions = sorted(parent._choices_actions, key=lambda x: x.dest) 200 for arg in args: 201 if arg[1]: 202 parser.add_argument(*arg[0], **arg[2]).completer = arg[1] 203 else: 204 parser.add_argument(*arg[0], **arg[2]) 205 parser.set_defaults(func=func) 206 return func 207 208 return decorator 209 210 211@command() 212def help(args=None): 213 """ 214 show this help message and exit 215 """ 216 p.print_help() 217 218 219def check_target_action(value): 220 """ 221 Check the target action option 222 223 :param value: str containing the value to check 224 """ 225 if value is None: 226 return None 227 228 if value in ("pause", "shutdown", "promote"): 229 return value 230 231 raise ArgumentTypeError("'%s' is not a valid recovery target action" % value) 232 233 234@command( 235 [argument("--minimal", help="machine readable output", action="store_true")], 236 cmd_aliases=["list-server"], 237) 238def list_servers(args): 239 """ 240 List available servers, with useful information 241 """ 242 # Get every server, both inactive and temporarily disabled 243 servers = get_server_list() 244 for name in sorted(servers): 245 server = servers[name] 246 247 # Exception: manage_server_command is not invoked here 248 # Normally you would call manage_server_command to check if the 249 # server is None and to report inactive and disabled servers, but here 250 # we want all servers and the server cannot be None 251 252 output.init("list_server", name, minimal=args.minimal) 253 description = server.config.description or "" 254 # If the server has been manually disabled 255 if not server.config.active: 256 description += " (inactive)" 257 # If server has configuration errors 258 elif server.config.disabled: 259 description += " (WARNING: disabled)" 260 # If server is a passive node 261 if server.passive_node: 262 description += " (Passive)" 263 output.result("list_server", name, description) 264 output.close_and_exit() 265 266 267@command( 268 [ 269 argument( 270 "--keep-descriptors", 271 help="Keep the stdout and the stderr streams attached to Barman subprocesses", 272 action="store_true", 273 ) 274 ] 275) 276def cron(args): 277 """ 278 Run maintenance tasks (global command) 279 """ 280 # Skip inactive and temporarily disabled servers 281 servers = get_server_list(skip_inactive=True, skip_disabled=True) 282 for name in sorted(servers): 283 server = servers[name] 284 285 # Exception: manage_server_command is not invoked here 286 # Normally you would call manage_server_command to check if the 287 # server is None and to report inactive and disabled servers, 288 # but here we have only active and well configured servers. 289 290 try: 291 server.cron(keep_descriptors=args.keep_descriptors) 292 except Exception: 293 # A cron should never raise an exception, so this code 294 # should never be executed. However, it is here to protect 295 # unrelated servers in case of unexpected failures. 296 output.exception( 297 "Unable to run cron on server '%s', " 298 "please look in the barman log file for more details.", 299 name, 300 ) 301 302 output.close_and_exit() 303 304 305# noinspection PyUnusedLocal 306def server_completer(prefix, parsed_args, **kwargs): 307 global_config(parsed_args) 308 for conf in barman.__config__.servers(): 309 if conf.name.startswith(prefix): 310 yield conf.name 311 312 313# noinspection PyUnusedLocal 314def server_completer_all(prefix, parsed_args, **kwargs): 315 global_config(parsed_args) 316 current_list = getattr(parsed_args, "server_name", None) or () 317 for conf in barman.__config__.servers(): 318 if conf.name.startswith(prefix) and conf.name not in current_list: 319 yield conf.name 320 if len(current_list) == 0 and "all".startswith(prefix): 321 yield "all" 322 323 324# noinspection PyUnusedLocal 325def backup_completer(prefix, parsed_args, **kwargs): 326 global_config(parsed_args) 327 server = get_server(parsed_args) 328 329 backups = server.get_available_backups() 330 for backup_id in sorted(backups, reverse=True): 331 if backup_id.startswith(prefix): 332 yield backup_id 333 for special_id in ("latest", "last", "oldest", "first", "last-failed"): 334 if len(backups) > 0 and special_id.startswith(prefix): 335 yield special_id 336 337 338@command( 339 [ 340 argument( 341 "server_name", 342 completer=server_completer_all, 343 nargs="+", 344 help="specifies the server names for the backup command " 345 "('all' will show all available servers)", 346 ), 347 argument( 348 "--immediate-checkpoint", 349 help="forces the initial checkpoint to be done as quickly as possible", 350 dest="immediate_checkpoint", 351 action="store_true", 352 default=SUPPRESS, 353 ), 354 argument( 355 "--no-immediate-checkpoint", 356 help="forces the initial checkpoint to be spread", 357 dest="immediate_checkpoint", 358 action="store_false", 359 default=SUPPRESS, 360 ), 361 argument( 362 "--reuse-backup", 363 nargs="?", 364 choices=barman.config.REUSE_BACKUP_VALUES, 365 default=None, 366 const="link", 367 help="use the previous backup to improve transfer-rate. " 368 'If no argument is given "link" is assumed', 369 ), 370 argument( 371 "--retry-times", 372 help="Number of retries after an error if base backup copy fails.", 373 type=check_non_negative, 374 ), 375 argument( 376 "--retry-sleep", 377 help="Wait time after a failed base backup copy, before retrying.", 378 type=check_non_negative, 379 ), 380 argument( 381 "--no-retry", 382 help="Disable base backup copy retry logic.", 383 dest="retry_times", 384 action="store_const", 385 const=0, 386 ), 387 argument( 388 "--jobs", 389 "-j", 390 help="Run the copy in parallel using NJOBS processes.", 391 type=check_positive, 392 metavar="NJOBS", 393 ), 394 argument( 395 "--bwlimit", 396 help="maximum transfer rate in kilobytes per second. " 397 "A value of 0 means no limit. Overrides 'bandwidth_limit' " 398 "configuration option.", 399 metavar="KBPS", 400 type=check_non_negative, 401 default=SUPPRESS, 402 ), 403 argument( 404 "--wait", 405 "-w", 406 help="wait for all the required WAL files to be archived", 407 dest="wait", 408 action="store_true", 409 default=False, 410 ), 411 argument( 412 "--wait-timeout", 413 help="the time, in seconds, spent waiting for the required " 414 "WAL files to be archived before timing out", 415 dest="wait_timeout", 416 metavar="TIMEOUT", 417 default=None, 418 type=check_non_negative, 419 ), 420 ] 421) 422def backup(args): 423 """ 424 Perform a full backup for the given server (supports 'all') 425 """ 426 servers = get_server_list(args, skip_inactive=True, skip_passive=True) 427 for name in sorted(servers): 428 server = servers[name] 429 430 # Skip the server (apply general rule) 431 if not manage_server_command(server, name): 432 continue 433 434 if args.reuse_backup is not None: 435 server.config.reuse_backup = args.reuse_backup 436 if args.retry_sleep is not None: 437 server.config.basebackup_retry_sleep = args.retry_sleep 438 if args.retry_times is not None: 439 server.config.basebackup_retry_times = args.retry_times 440 if hasattr(args, "immediate_checkpoint"): 441 server.config.immediate_checkpoint = args.immediate_checkpoint 442 if args.jobs is not None: 443 server.config.parallel_jobs = args.jobs 444 if hasattr(args, "bwlimit"): 445 server.config.bandwidth_limit = args.bwlimit 446 with closing(server): 447 server.backup(wait=args.wait, wait_timeout=args.wait_timeout) 448 output.close_and_exit() 449 450 451@command( 452 [ 453 argument( 454 "server_name", 455 completer=server_completer_all, 456 nargs="+", 457 help="specifies the server name for the command " 458 "('all' will show all available servers)", 459 ), 460 argument("--minimal", help="machine readable output", action="store_true"), 461 ], 462 cmd_aliases=["list-backup"], 463) 464def list_backups(args): 465 """ 466 List available backups for the given server (supports 'all') 467 """ 468 servers = get_server_list(args, skip_inactive=True) 469 for name in sorted(servers): 470 server = servers[name] 471 472 # Skip the server (apply general rule) 473 if not manage_server_command(server, name): 474 continue 475 476 output.init("list_backup", name, minimal=args.minimal) 477 with closing(server): 478 server.list_backups() 479 output.close_and_exit() 480 481 482@command( 483 [ 484 argument( 485 "server_name", 486 completer=server_completer_all, 487 nargs="+", 488 help="specifies the server name for the command", 489 ) 490 ] 491) 492def status(args): 493 """ 494 Shows live information and status of the PostgreSQL server 495 """ 496 servers = get_server_list(args, skip_inactive=True) 497 for name in sorted(servers): 498 server = servers[name] 499 500 # Skip the server (apply general rule) 501 if not manage_server_command(server, name): 502 continue 503 504 output.init("status", name) 505 with closing(server): 506 server.status() 507 output.close_and_exit() 508 509 510@command( 511 [ 512 argument( 513 "server_name", 514 completer=server_completer_all, 515 nargs="+", 516 help="specifies the server name for the command " 517 "('all' will show all available servers)", 518 ), 519 argument("--minimal", help="machine readable output", action="store_true"), 520 argument( 521 "--target", 522 choices=("all", "hot-standby", "wal-streamer"), 523 default="all", 524 help=""" 525 Possible values are: 'hot-standby' (only hot standby servers), 526 'wal-streamer' (only WAL streaming clients, such as pg_receivewal), 527 'all' (any of them). Defaults to %(default)s""", 528 ), 529 ] 530) 531def replication_status(args): 532 """ 533 Shows live information and status of any streaming client 534 """ 535 servers = get_server_list(args, skip_inactive=True) 536 for name in sorted(servers): 537 server = servers[name] 538 539 # Skip the server (apply general rule) 540 if not manage_server_command(server, name): 541 continue 542 543 with closing(server): 544 output.init("replication_status", name, minimal=args.minimal) 545 server.replication_status(args.target) 546 output.close_and_exit() 547 548 549@command( 550 [ 551 argument( 552 "server_name", 553 completer=server_completer_all, 554 nargs="+", 555 help="specifies the server name for the command ", 556 ) 557 ] 558) 559def rebuild_xlogdb(args): 560 """ 561 Rebuild the WAL file database guessing it from the disk content. 562 """ 563 servers = get_server_list(args, skip_inactive=True) 564 for name in sorted(servers): 565 server = servers[name] 566 567 # Skip the server (apply general rule) 568 if not manage_server_command(server, name): 569 continue 570 571 with closing(server): 572 server.rebuild_xlogdb() 573 output.close_and_exit() 574 575 576@command( 577 [ 578 argument( 579 "server_name", 580 completer=server_completer, 581 help="specifies the server name for the command ", 582 ), 583 argument("--target-tli", help="target timeline", type=check_positive), 584 argument( 585 "--target-time", 586 help="target time. You can use any valid unambiguous representation. " 587 'e.g: "YYYY-MM-DD HH:MM:SS.mmm"', 588 ), 589 argument("--target-xid", help="target transaction ID"), 590 argument("--target-lsn", help="target LSN (Log Sequence Number)"), 591 argument( 592 "--target-name", 593 help="target name created previously with " 594 "pg_create_restore_point() function call", 595 ), 596 argument( 597 "--target-immediate", 598 help="end recovery as soon as a consistent state is reached", 599 action="store_true", 600 default=False, 601 ), 602 argument( 603 "--exclusive", help="set target to be non inclusive", action="store_true" 604 ), 605 argument( 606 "--tablespace", 607 help="tablespace relocation rule", 608 metavar="NAME:LOCATION", 609 action="append", 610 ), 611 argument( 612 "--remote-ssh-command", 613 metavar="SSH_COMMAND", 614 help="This options activates remote recovery, by specifying the secure " 615 "shell command to be launched on a remote host. It is " 616 'the equivalent of the "ssh_command" server option in ' 617 "the configuration file for remote recovery. " 618 'Example: "ssh postgres@db2"', 619 ), 620 argument( 621 "backup_id", 622 completer=backup_completer, 623 help="specifies the backup ID to recover", 624 ), 625 argument( 626 "destination_directory", 627 help="the directory where the new server is created", 628 ), 629 argument( 630 "--bwlimit", 631 help="maximum transfer rate in kilobytes per second. " 632 "A value of 0 means no limit. Overrides 'bandwidth_limit' " 633 "configuration option.", 634 metavar="KBPS", 635 type=check_non_negative, 636 default=SUPPRESS, 637 ), 638 argument( 639 "--retry-times", 640 help="Number of retries after an error if base backup copy fails.", 641 type=check_non_negative, 642 ), 643 argument( 644 "--retry-sleep", 645 help="Wait time after a failed base backup copy, before retrying.", 646 type=check_non_negative, 647 ), 648 argument( 649 "--no-retry", 650 help="Disable base backup copy retry logic.", 651 dest="retry_times", 652 action="store_const", 653 const=0, 654 ), 655 argument( 656 "--jobs", 657 "-j", 658 help="Run the copy in parallel using NJOBS processes.", 659 type=check_positive, 660 metavar="NJOBS", 661 ), 662 argument( 663 "--get-wal", 664 help="Enable the get-wal option during the recovery.", 665 dest="get_wal", 666 action="store_true", 667 default=SUPPRESS, 668 ), 669 argument( 670 "--no-get-wal", 671 help="Disable the get-wal option during recovery.", 672 dest="get_wal", 673 action="store_false", 674 default=SUPPRESS, 675 ), 676 argument( 677 "--network-compression", 678 help="Enable network compression during remote recovery.", 679 dest="network_compression", 680 action="store_true", 681 default=SUPPRESS, 682 ), 683 argument( 684 "--no-network-compression", 685 help="Disable network compression during remote recovery.", 686 dest="network_compression", 687 action="store_false", 688 default=SUPPRESS, 689 ), 690 argument( 691 "--target-action", 692 help="Specifies what action the server should take once the " 693 "recovery target is reached. This option is not allowed for " 694 "PostgreSQL < 9.1. If PostgreSQL is between 9.1 and 9.4 included " 695 'the only allowed value is "pause". If PostgreSQL is 9.5 or newer ' 696 'the possible values are "shutdown", "pause", "promote".', 697 dest="target_action", 698 type=check_target_action, 699 default=SUPPRESS, 700 ), 701 argument( 702 "--standby-mode", 703 dest="standby_mode", 704 action="store_true", 705 default=SUPPRESS, 706 help="Enable standby mode when starting the recovered PostgreSQL instance", 707 ), 708 ] 709) 710def recover(args): 711 """ 712 Recover a server at a given time, name, LSN or xid 713 """ 714 server = get_server(args) 715 716 # Retrieves the backup 717 backup_id = parse_backup_id(server, args) 718 if backup_id.status not in BackupInfo.STATUS_COPY_DONE: 719 output.error( 720 "Cannot recover from backup '%s' of server '%s': " 721 "backup status is not DONE", 722 args.backup_id, 723 server.config.name, 724 ) 725 output.close_and_exit() 726 727 # decode the tablespace relocation rules 728 tablespaces = {} 729 if args.tablespace: 730 for rule in args.tablespace: 731 try: 732 tablespaces.update([rule.split(":", 1)]) 733 except ValueError: 734 output.error( 735 "Invalid tablespace relocation rule '%s'\n" 736 "HINT: The valid syntax for a relocation rule is " 737 "NAME:LOCATION", 738 rule, 739 ) 740 output.close_and_exit() 741 742 # validate the rules against the tablespace list 743 valid_tablespaces = [] 744 if backup_id.tablespaces: 745 valid_tablespaces = [ 746 tablespace_data.name for tablespace_data in backup_id.tablespaces 747 ] 748 for item in tablespaces: 749 if item not in valid_tablespaces: 750 output.error( 751 "Invalid tablespace name '%s'\n" 752 "HINT: Please use any of the following " 753 "tablespaces: %s", 754 item, 755 ", ".join(valid_tablespaces), 756 ) 757 output.close_and_exit() 758 759 # explicitly disallow the rsync remote syntax (common mistake) 760 if ":" in args.destination_directory: 761 output.error( 762 "The destination directory parameter " 763 "cannot contain the ':' character\n" 764 "HINT: If you want to do a remote recovery you have to use " 765 "the --remote-ssh-command option" 766 ) 767 output.close_and_exit() 768 if args.retry_sleep is not None: 769 server.config.basebackup_retry_sleep = args.retry_sleep 770 if args.retry_times is not None: 771 server.config.basebackup_retry_times = args.retry_times 772 if hasattr(args, "get_wal"): 773 if args.get_wal: 774 server.config.recovery_options.add(RecoveryOptions.GET_WAL) 775 else: 776 server.config.recovery_options.remove(RecoveryOptions.GET_WAL) 777 if args.jobs is not None: 778 server.config.parallel_jobs = args.jobs 779 if hasattr(args, "bwlimit"): 780 server.config.bandwidth_limit = args.bwlimit 781 782 # PostgreSQL supports multiple parameters to specify when the recovery 783 # process will end, and in that case the last entry in recovery 784 # configuration files will be used. See [1] 785 # 786 # Since the meaning of the target options is not dependent on the order 787 # of parameters, we decided to make the target options mutually exclusive. 788 # 789 # [1]: https://www.postgresql.org/docs/current/static/ 790 # recovery-target-settings.html 791 792 target_options = [ 793 "target_time", 794 "target_xid", 795 "target_lsn", 796 "target_name", 797 "target_immediate", 798 ] 799 specified_target_options = len( 800 [option for option in target_options if getattr(args, option)] 801 ) 802 if specified_target_options > 1: 803 output.error("You cannot specify multiple targets for the recovery operation") 804 output.close_and_exit() 805 806 if hasattr(args, "network_compression"): 807 if args.network_compression and args.remote_ssh_command is None: 808 output.error( 809 "Network compression can only be used with " 810 "remote recovery.\n" 811 "HINT: If you want to do a remote recovery " 812 "you have to use the --remote-ssh-command option" 813 ) 814 output.close_and_exit() 815 server.config.network_compression = args.network_compression 816 817 with closing(server): 818 try: 819 server.recover( 820 backup_id, 821 args.destination_directory, 822 tablespaces=tablespaces, 823 target_tli=args.target_tli, 824 target_time=args.target_time, 825 target_xid=args.target_xid, 826 target_lsn=args.target_lsn, 827 target_name=args.target_name, 828 target_immediate=args.target_immediate, 829 exclusive=args.exclusive, 830 remote_command=args.remote_ssh_command, 831 target_action=getattr(args, "target_action", None), 832 standby_mode=getattr(args, "standby_mode", None), 833 ) 834 except RecoveryException as exc: 835 output.error(force_str(exc)) 836 837 output.close_and_exit() 838 839 840@command( 841 [ 842 argument( 843 "server_name", 844 completer=server_completer_all, 845 nargs="+", 846 help="specifies the server names to show " 847 "('all' will show all available servers)", 848 ) 849 ], 850 cmd_aliases=["show-server"], 851) 852def show_servers(args): 853 """ 854 Show all configuration parameters for the specified servers 855 """ 856 servers = get_server_list(args) 857 for name in sorted(servers): 858 server = servers[name] 859 860 # Skip the server (apply general rule) 861 if not manage_server_command( 862 server, 863 name, 864 skip_inactive=False, 865 skip_disabled=False, 866 disabled_is_error=False, 867 ): 868 continue 869 870 # If the server has been manually disabled 871 if not server.config.active: 872 name += " (inactive)" 873 # If server has configuration errors 874 elif server.config.disabled: 875 name += " (WARNING: disabled)" 876 output.init("show_server", name) 877 with closing(server): 878 server.show() 879 output.close_and_exit() 880 881 882@command( 883 [ 884 argument( 885 "server_name", 886 completer=server_completer_all, 887 nargs="+", 888 help="specifies the server name target of the switch-wal command", 889 ), 890 argument( 891 "--force", 892 help="forces the switch of a WAL by executing a checkpoint before", 893 dest="force", 894 action="store_true", 895 default=False, 896 ), 897 argument( 898 "--archive", 899 help="wait for one WAL file to be archived", 900 dest="archive", 901 action="store_true", 902 default=False, 903 ), 904 argument( 905 "--archive-timeout", 906 help="the time, in seconds, the archiver will wait for a new WAL file " 907 "to be archived before timing out", 908 metavar="TIMEOUT", 909 default="30", 910 type=check_non_negative, 911 ), 912 ], 913 cmd_aliases=["switch-xlog"], 914) 915def switch_wal(args): 916 """ 917 Execute the switch-wal command on the target server 918 """ 919 servers = get_server_list(args, skip_inactive=True) 920 for name in sorted(servers): 921 server = servers[name] 922 # Skip the server (apply general rule) 923 if not manage_server_command(server, name): 924 continue 925 with closing(server): 926 server.switch_wal(args.force, args.archive, args.archive_timeout) 927 output.close_and_exit() 928 929 930@command( 931 [ 932 argument( 933 "server_name", 934 completer=server_completer_all, 935 nargs="+", 936 help="specifies the server names to check " 937 "('all' will check all available servers)", 938 ), 939 argument( 940 "--nagios", help="Nagios plugin compatible output", action="store_true" 941 ), 942 ] 943) 944def check(args): 945 """ 946 Check if the server configuration is working. 947 948 This command returns success if every checks pass, 949 or failure if any of these fails 950 """ 951 if args.nagios: 952 output.set_output_writer(output.NagiosOutputWriter()) 953 servers = get_server_list(args) 954 for name in sorted(servers): 955 server = servers[name] 956 957 # Validate the returned server 958 if not manage_server_command( 959 server, 960 name, 961 skip_inactive=False, 962 skip_disabled=False, 963 disabled_is_error=False, 964 ): 965 continue 966 967 output.init("check", name, server.config.active, server.config.disabled) 968 with closing(server): 969 server.check() 970 output.close_and_exit() 971 972 973@command() 974def diagnose(args=None): 975 """ 976 Diagnostic command (for support and problems detection purpose) 977 """ 978 # Get every server (both inactive and temporarily disabled) 979 servers = get_server_list(on_error_stop=False, suppress_error=True) 980 # errors list with duplicate paths between servers 981 errors_list = barman.__config__.servers_msg_list 982 barman.diagnose.exec_diagnose(servers, errors_list) 983 output.close_and_exit() 984 985 986@command( 987 [ 988 argument( 989 "--primary", 990 help="execute the sync-info on the primary node (if set)", 991 action="store_true", 992 default=SUPPRESS, 993 ), 994 argument( 995 "server_name", 996 completer=server_completer, 997 help="specifies the server name for the command", 998 ), 999 argument( 1000 "last_wal", help="specifies the name of the latest WAL read", nargs="?" 1001 ), 1002 argument( 1003 "last_position", 1004 nargs="?", 1005 type=check_positive, 1006 help="the last position read from xlog database (in bytes)", 1007 ), 1008 ] 1009) 1010def sync_info(args): 1011 """ 1012 Output the internal synchronisation status. 1013 Used to sync_backup with a passive node 1014 """ 1015 server = get_server(args) 1016 try: 1017 # if called with --primary option 1018 if getattr(args, "primary", False): 1019 primary_info = server.primary_node_info(args.last_wal, args.last_position) 1020 output.info( 1021 json.dumps(primary_info, cls=BarmanEncoder, indent=4), log=False 1022 ) 1023 else: 1024 server.sync_status(args.last_wal, args.last_position) 1025 except SyncError as e: 1026 # Catch SyncError exceptions and output only the error message, 1027 # preventing from logging the stack trace 1028 output.error(e) 1029 1030 output.close_and_exit() 1031 1032 1033@command( 1034 [ 1035 argument( 1036 "server_name", 1037 completer=server_completer, 1038 help="specifies the server name for the command", 1039 ), 1040 argument( 1041 "backup_id", help="specifies the backup ID to be copied on the passive node" 1042 ), 1043 ] 1044) 1045def sync_backup(args): 1046 """ 1047 Command that synchronises a backup from a master to a passive node 1048 """ 1049 server = get_server(args) 1050 try: 1051 server.sync_backup(args.backup_id) 1052 except SyncError as e: 1053 # Catch SyncError exceptions and output only the error message, 1054 # preventing from logging the stack trace 1055 output.error(e) 1056 output.close_and_exit() 1057 1058 1059@command( 1060 [ 1061 argument( 1062 "server_name", 1063 completer=server_completer, 1064 help="specifies the server name for the command", 1065 ) 1066 ] 1067) 1068def sync_wals(args): 1069 """ 1070 Command that synchronises WAL files from a master to a passive node 1071 """ 1072 server = get_server(args) 1073 try: 1074 server.sync_wals() 1075 except SyncError as e: 1076 # Catch SyncError exceptions and output only the error message, 1077 # preventing from logging the stack trace 1078 output.error(e) 1079 output.close_and_exit() 1080 1081 1082@command( 1083 [ 1084 argument( 1085 "server_name", 1086 completer=server_completer, 1087 help="specifies the server name for the command", 1088 ), 1089 argument( 1090 "backup_id", completer=backup_completer, help="specifies the backup ID" 1091 ), 1092 ], 1093 cmd_aliases=["show-backups"], 1094) 1095def show_backup(args): 1096 """ 1097 This method shows a single backup information 1098 """ 1099 server = get_server(args) 1100 1101 # Retrieves the backup 1102 backup_info = parse_backup_id(server, args) 1103 with closing(server): 1104 server.show_backup(backup_info) 1105 output.close_and_exit() 1106 1107 1108@command( 1109 [ 1110 argument( 1111 "server_name", 1112 completer=server_completer, 1113 help="specifies the server name for the command", 1114 ), 1115 argument( 1116 "backup_id", completer=backup_completer, help="specifies the backup ID" 1117 ), 1118 argument( 1119 "--target", 1120 choices=("standalone", "data", "wal", "full"), 1121 default="standalone", 1122 help=""" 1123 Possible values are: data (just the data files), standalone 1124 (base backup files, including required WAL files), 1125 wal (just WAL files between the beginning of base 1126 backup and the following one (if any) or the end of the log) and 1127 full (same as data + wal). Defaults to %(default)s""", 1128 ), 1129 ] 1130) 1131def list_files(args): 1132 """ 1133 List all the files for a single backup 1134 """ 1135 server = get_server(args) 1136 1137 # Retrieves the backup 1138 backup_info = parse_backup_id(server, args) 1139 try: 1140 for line in backup_info.get_list_of_files(args.target): 1141 output.info(line, log=False) 1142 except BadXlogSegmentName as e: 1143 output.error( 1144 "invalid xlog segment name %r\n" 1145 'HINT: Please run "barman rebuild-xlogdb %s" ' 1146 "to solve this issue", 1147 force_str(e), 1148 server.config.name, 1149 ) 1150 output.close_and_exit() 1151 1152 1153@command( 1154 [ 1155 argument( 1156 "server_name", 1157 completer=server_completer, 1158 help="specifies the server name for the command", 1159 ), 1160 argument( 1161 "backup_id", completer=backup_completer, help="specifies the backup ID" 1162 ), 1163 ] 1164) 1165def delete(args): 1166 """ 1167 Delete a backup 1168 """ 1169 server = get_server(args) 1170 1171 # Retrieves the backup 1172 backup_id = parse_backup_id(server, args) 1173 with closing(server): 1174 if not server.delete_backup(backup_id): 1175 output.error( 1176 "Cannot delete backup (%s %s)" % (server.config.name, backup_id) 1177 ) 1178 output.close_and_exit() 1179 1180 1181@command( 1182 [ 1183 argument( 1184 "server_name", 1185 completer=server_completer, 1186 help="specifies the server name for the command", 1187 ), 1188 argument("wal_name", help="the WAL file to get"), 1189 argument( 1190 "--output-directory", 1191 "-o", 1192 help="put the retrieved WAL file in this directory with the original name", 1193 default=SUPPRESS, 1194 ), 1195 argument( 1196 "--partial", 1197 "-P", 1198 help="retrieve also partial WAL files (.partial)", 1199 action="store_true", 1200 dest="partial", 1201 default=False, 1202 ), 1203 argument( 1204 "--gzip", 1205 "-z", 1206 "-x", 1207 help="compress the output with gzip", 1208 action="store_const", 1209 const="gzip", 1210 dest="compression", 1211 default=SUPPRESS, 1212 ), 1213 argument( 1214 "--bzip2", 1215 "-j", 1216 help="compress the output with bzip2", 1217 action="store_const", 1218 const="bzip2", 1219 dest="compression", 1220 default=SUPPRESS, 1221 ), 1222 argument( 1223 "--peek", 1224 "-p", 1225 help="peek from the WAL archive up to 'SIZE' WAL files, starting " 1226 "from the requested one. 'SIZE' must be an integer >= 1. " 1227 "When invoked with this option, get-wal returns a list of " 1228 "zero to 'SIZE' WAL segment names, one per row.", 1229 metavar="SIZE", 1230 type=check_positive, 1231 default=SUPPRESS, 1232 ), 1233 argument( 1234 "--test", 1235 "-t", 1236 help="test both the connection and the configuration of the requested " 1237 "PostgreSQL server in Barman for WAL retrieval. With this option, " 1238 "the 'wal_name' mandatory argument is ignored.", 1239 action="store_true", 1240 default=SUPPRESS, 1241 ), 1242 ] 1243) 1244def get_wal(args): 1245 """ 1246 Retrieve WAL_NAME file from SERVER_NAME archive. 1247 The content will be streamed on standard output unless 1248 the --output-directory option is specified. 1249 """ 1250 server = get_server(args, inactive_is_error=True) 1251 1252 if getattr(args, "test", None): 1253 output.info( 1254 "Ready to retrieve WAL files from the server %s", server.config.name 1255 ) 1256 return 1257 1258 # Retrieve optional arguments. If an argument is not specified, 1259 # the namespace doesn't contain it due to SUPPRESS default. 1260 # In that case we pick 'None' using getattr third argument. 1261 compression = getattr(args, "compression", None) 1262 output_directory = getattr(args, "output_directory", None) 1263 peek = getattr(args, "peek", None) 1264 1265 with closing(server): 1266 server.get_wal( 1267 args.wal_name, 1268 compression=compression, 1269 output_directory=output_directory, 1270 peek=peek, 1271 partial=args.partial, 1272 ) 1273 output.close_and_exit() 1274 1275 1276@command( 1277 [ 1278 argument( 1279 "server_name", 1280 completer=server_completer, 1281 help="specifies the server name for the command", 1282 ), 1283 argument( 1284 "--test", 1285 "-t", 1286 help="test both the connection and the configuration of the requested " 1287 "PostgreSQL server in Barman to make sure it is ready to receive " 1288 "WAL files.", 1289 action="store_true", 1290 default=SUPPRESS, 1291 ), 1292 ] 1293) 1294def put_wal(args): 1295 """ 1296 Receive a WAL file from SERVER_NAME and securely store it in the incoming 1297 directory. The file will be read from standard input in tar format. 1298 """ 1299 server = get_server(args, inactive_is_error=True) 1300 1301 if getattr(args, "test", None): 1302 output.info("Ready to accept WAL files for the server %s", server.config.name) 1303 return 1304 1305 try: 1306 # Python 3.x 1307 stream = sys.stdin.buffer 1308 except AttributeError: 1309 # Python 2.x 1310 stream = sys.stdin 1311 with closing(server): 1312 server.put_wal(stream) 1313 output.close_and_exit() 1314 1315 1316@command( 1317 [ 1318 argument( 1319 "server_name", 1320 completer=server_completer, 1321 help="specifies the server name for the command", 1322 ) 1323 ] 1324) 1325def archive_wal(args): 1326 """ 1327 Execute maintenance operations on WAL files for a given server. 1328 This command processes any incoming WAL files for the server 1329 and archives them along the catalogue. 1330 1331 """ 1332 server = get_server(args) 1333 with closing(server): 1334 server.archive_wal() 1335 output.close_and_exit() 1336 1337 1338@command( 1339 [ 1340 argument( 1341 "--stop", 1342 help="stop the receive-wal subprocess for the server", 1343 action="store_true", 1344 ), 1345 argument( 1346 "--reset", 1347 help="reset the status of receive-wal removing any status files", 1348 action="store_true", 1349 ), 1350 argument( 1351 "--create-slot", 1352 help="create the replication slot, if it does not exist", 1353 action="store_true", 1354 ), 1355 argument( 1356 "--drop-slot", 1357 help="drop the replication slot, if it exists", 1358 action="store_true", 1359 ), 1360 argument( 1361 "server_name", 1362 completer=server_completer, 1363 help="specifies the server name for the command", 1364 ), 1365 ] 1366) 1367def receive_wal(args): 1368 """ 1369 Start a receive-wal process. 1370 The process uses the streaming protocol to receive WAL files 1371 from the PostgreSQL server. 1372 """ 1373 server = get_server(args) 1374 if args.stop and args.reset: 1375 output.error("--stop and --reset options are not compatible") 1376 # If the caller requested to shutdown the receive-wal process deliver the 1377 # termination signal, otherwise attempt to start it 1378 elif args.stop: 1379 server.kill("receive-wal") 1380 elif args.create_slot: 1381 with closing(server): 1382 server.create_physical_repslot() 1383 elif args.drop_slot: 1384 with closing(server): 1385 server.drop_repslot() 1386 else: 1387 with closing(server): 1388 server.receive_wal(reset=args.reset) 1389 output.close_and_exit() 1390 1391 1392@command( 1393 [ 1394 argument( 1395 "server_name", 1396 completer=server_completer, 1397 help="specifies the server name for the command", 1398 ), 1399 argument( 1400 "backup_id", completer=backup_completer, help="specifies the backup ID" 1401 ), 1402 ] 1403) 1404def check_backup(args): 1405 """ 1406 Make sure that all the required WAL files to check 1407 the consistency of a physical backup (that is, from the 1408 beginning to the end of the full backup) are correctly 1409 archived. This command is automatically invoked by the 1410 cron command and at the end of every backup operation. 1411 """ 1412 server = get_server(args) 1413 1414 # Retrieves the backup 1415 backup_info = parse_backup_id(server, args) 1416 1417 with closing(server): 1418 server.check_backup(backup_info) 1419 output.close_and_exit() 1420 1421 1422@command( 1423 [ 1424 argument( 1425 "server_name", 1426 completer=server_completer, 1427 help="specifies the server name for the command", 1428 ), 1429 argument( 1430 "backup_id", completer=backup_completer, help="specifies the backup ID" 1431 ), 1432 argument("--release", help="remove the keep annotation", action="store_true"), 1433 argument( 1434 "--status", help="return the keep status of the backup", action="store_true" 1435 ), 1436 argument( 1437 "--target", 1438 help="keep this backup with the specified recovery target", 1439 choices=[KeepManager.TARGET_FULL, KeepManager.TARGET_STANDALONE], 1440 ), 1441 ] 1442) 1443def keep(args): 1444 """ 1445 Tag the specified backup so that it will never be deleted 1446 """ 1447 if not any((args.release, args.status, args.target)): 1448 output.error( 1449 "one of the arguments -r/--release -s/--status --target is required" 1450 ) 1451 output.close_and_exit() 1452 server = get_server(args) 1453 backup_info = parse_backup_id(server, args) 1454 backup_manager = server.backup_manager 1455 if args.status: 1456 output.init("status", server.config.name) 1457 target = backup_manager.get_keep_target(backup_info.backup_id) 1458 if target: 1459 output.result("status", server.config.name, "keep_status", "Keep", target) 1460 else: 1461 output.result("status", server.config.name, "keep_status", "Keep", "nokeep") 1462 elif args.release: 1463 backup_manager.release_keep(backup_info.backup_id) 1464 else: 1465 if backup_info.status != BackupInfo.DONE: 1466 msg = ( 1467 "Cannot add keep to backup %s because it has status %s. " 1468 "Only backups with status DONE can be kept." 1469 ) % (backup_info.backup_id, backup_info.status) 1470 output.error(msg) 1471 output.close_and_exit() 1472 backup_manager.keep_backup(backup_info.backup_id, args.target) 1473 1474 1475@command( 1476 [ 1477 argument( 1478 "server_name", 1479 completer=server_completer, 1480 help="specifies the server name for the command", 1481 ), 1482 argument( 1483 "--timeline", 1484 help="the earliest timeline whose WALs should cause the check to fail", 1485 type=check_positive, 1486 ), 1487 ] 1488) 1489def check_wal_archive(args): 1490 """ 1491 Check the WAL archive can be safely used for a new server. 1492 1493 This will fail if there are any existing WALs in the archive. 1494 If the --timeline option is used then any WALs on earlier timelines 1495 than that specified will not cause the check to fail. 1496 """ 1497 server = get_server(args) 1498 output.init("check_wal_archive", server.config.name) 1499 1500 with server.xlogdb() as fxlogdb: 1501 wals = [WalFileInfo.from_xlogdb_line(w).name for w in fxlogdb] 1502 try: 1503 check_archive_usable( 1504 wals, 1505 timeline=args.timeline, 1506 ) 1507 output.result("check_wal_archive", server.config.name) 1508 except WalArchiveContentError as err: 1509 msg = "WAL archive check failed for server %s: %s" % ( 1510 server.config.name, 1511 force_str(err), 1512 ) 1513 logging.error(msg) 1514 output.error(msg) 1515 output.close_and_exit() 1516 1517 1518def pretty_args(args): 1519 """ 1520 Prettify the given argparse namespace to be human readable 1521 1522 :type args: argparse.Namespace 1523 :return: the human readable content of the namespace 1524 """ 1525 values = dict(vars(args)) 1526 # Retrieve the command name with recent argh versions 1527 if "_functions_stack" in values: 1528 values["command"] = values["_functions_stack"][0].__name__ 1529 del values["_functions_stack"] 1530 # Older argh versions only have the matching function in the namespace 1531 elif "function" in values: 1532 values["command"] = values["function"].__name__ 1533 del values["function"] 1534 return "%r" % values 1535 1536 1537def global_config(args): 1538 """ 1539 Set the configuration file 1540 """ 1541 if hasattr(args, "config"): 1542 filename = args.config 1543 else: 1544 try: 1545 filename = os.environ["BARMAN_CONFIG_FILE"] 1546 except KeyError: 1547 filename = None 1548 config = barman.config.Config(filename) 1549 barman.__config__ = config 1550 1551 # change user if needed 1552 try: 1553 drop_privileges(config.user) 1554 except OSError: 1555 msg = "ERROR: please run barman as %r user" % config.user 1556 raise SystemExit(msg) 1557 except KeyError: 1558 msg = "ERROR: the configured user %r does not exists" % config.user 1559 raise SystemExit(msg) 1560 1561 # configure logging 1562 if hasattr(args, "log_level"): 1563 config.log_level = args.log_level 1564 log_level = parse_log_level(config.log_level) 1565 configure_logging( 1566 config.log_file, log_level or barman.config.DEFAULT_LOG_LEVEL, config.log_format 1567 ) 1568 if log_level is None: 1569 _logger.warning("unknown log_level in config file: %s", config.log_level) 1570 1571 # Configure output 1572 if args.format != output.DEFAULT_WRITER or args.quiet or args.debug: 1573 output.set_output_writer(args.format, quiet=args.quiet, debug=args.debug) 1574 1575 # Configure color output 1576 if args.color == "auto": 1577 # Enable colored output if both stdout and stderr are TTYs 1578 output.ansi_colors_enabled = sys.stdout.isatty() and sys.stderr.isatty() 1579 else: 1580 output.ansi_colors_enabled = args.color == "always" 1581 1582 # Load additional configuration files 1583 config.load_configuration_files_directory() 1584 # We must validate the configuration here in order to have 1585 # both output and logging configured 1586 config.validate_global_config() 1587 1588 _logger.debug( 1589 "Initialised Barman version %s (config: %s, args: %s)", 1590 barman.__version__, 1591 config.config_file, 1592 pretty_args(args), 1593 ) 1594 1595 1596def get_server( 1597 args, 1598 skip_inactive=True, 1599 skip_disabled=False, 1600 skip_passive=False, 1601 inactive_is_error=False, 1602 on_error_stop=True, 1603 suppress_error=False, 1604): 1605 """ 1606 Get a single server retrieving its configuration (wraps get_server_list()) 1607 1608 Returns a Server object or None if the required server is unknown and 1609 on_error_stop is False. 1610 1611 WARNING: this function modifies the 'args' parameter 1612 1613 :param args: an argparse namespace containing a single 1614 server_name parameter 1615 WARNING: the function modifies the content of this parameter 1616 :param bool skip_inactive: do nothing if the server is inactive 1617 :param bool skip_disabled: do nothing if the server is disabled 1618 :param bool skip_passive: do nothing if the server is passive 1619 :param bool inactive_is_error: treat inactive server as error 1620 :param bool on_error_stop: stop if an error is found 1621 :param bool suppress_error: suppress display of errors (e.g. diagnose) 1622 :rtype: Server|None 1623 """ 1624 # This function must to be called with in a single-server context 1625 name = args.server_name 1626 assert isinstance(name, str) 1627 1628 # The 'all' special name is forbidden in this context 1629 if name == "all": 1630 output.error("You cannot use 'all' in a single server context") 1631 output.close_and_exit() 1632 # The following return statement will never be reached 1633 # but it is here for clarity 1634 return None 1635 1636 # Builds a list from a single given name 1637 args.server_name = [name] 1638 1639 # Skip_inactive is reset if inactive_is_error is set, because 1640 # it needs to retrieve the inactive server to emit the error. 1641 skip_inactive &= not inactive_is_error 1642 1643 # Retrieve the requested server 1644 servers = get_server_list( 1645 args, skip_inactive, skip_disabled, skip_passive, on_error_stop, suppress_error 1646 ) 1647 1648 # The requested server has been excluded from get_server_list result 1649 if len(servers) == 0: 1650 output.close_and_exit() 1651 # The following return statement will never be reached 1652 # but it is here for clarity 1653 return None 1654 1655 # retrieve the server object 1656 server = servers[name] 1657 1658 # Apply standard validation control and skips 1659 # the server if inactive or disabled, displaying standard 1660 # error messages. If on_error_stop (default) exits 1661 if not manage_server_command(server, name, inactive_is_error) and on_error_stop: 1662 output.close_and_exit() 1663 # The following return statement will never be reached 1664 # but it is here for clarity 1665 return None 1666 1667 # Returns the filtered server 1668 return server 1669 1670 1671def get_server_list( 1672 args=None, 1673 skip_inactive=False, 1674 skip_disabled=False, 1675 skip_passive=False, 1676 on_error_stop=True, 1677 suppress_error=False, 1678): 1679 """ 1680 Get the server list from the configuration 1681 1682 If args the parameter is None or arg.server_name is ['all'] 1683 returns all defined servers 1684 1685 :param args: an argparse namespace containing a list server_name parameter 1686 :param bool skip_inactive: skip inactive servers when 'all' is required 1687 :param bool skip_disabled: skip disabled servers when 'all' is required 1688 :param bool skip_passive: skip passive servers when 'all' is required 1689 :param bool on_error_stop: stop if an error is found 1690 :param bool suppress_error: suppress display of errors (e.g. diagnose) 1691 :rtype: dict[str,Server] 1692 """ 1693 server_dict = {} 1694 1695 # This function must to be called with in a multiple-server context 1696 assert not args or isinstance(args.server_name, list) 1697 1698 # Generate the list of servers (required for global errors) 1699 available_servers = barman.__config__.server_names() 1700 1701 # Get a list of configuration errors from all the servers 1702 global_error_list = barman.__config__.servers_msg_list 1703 1704 # Global errors have higher priority 1705 if global_error_list: 1706 # Output the list of global errors 1707 if not suppress_error: 1708 for error in global_error_list: 1709 output.error(error) 1710 1711 # If requested, exit on first error 1712 if on_error_stop: 1713 output.close_and_exit() 1714 # The following return statement will never be reached 1715 # but it is here for clarity 1716 return {} 1717 1718 # Handle special 'all' server cases 1719 # - args is None 1720 # - 'all' special name 1721 if not args or "all" in args.server_name: 1722 # When 'all' is used, it must be the only specified argument 1723 if args and len(args.server_name) != 1: 1724 output.error("You cannot use 'all' with other server names") 1725 servers = available_servers 1726 else: 1727 # Put servers in a set, so multiple occurrences are counted only once 1728 servers = set(args.server_name) 1729 1730 # Loop through all the requested servers 1731 for server in servers: 1732 conf = barman.__config__.get_server(server) 1733 if conf is None: 1734 # Unknown server 1735 server_dict[server] = None 1736 else: 1737 server_object = Server(conf) 1738 # Skip inactive servers, if requested 1739 if skip_inactive and not server_object.config.active: 1740 output.info("Skipping inactive server '%s'" % conf.name) 1741 continue 1742 # Skip disabled servers, if requested 1743 if skip_disabled and server_object.config.disabled: 1744 output.info("Skipping temporarily disabled server '%s'" % conf.name) 1745 continue 1746 # Skip passive nodes, if requested 1747 if skip_passive and server_object.passive_node: 1748 output.info("Skipping passive server '%s'", conf.name) 1749 continue 1750 server_dict[server] = server_object 1751 1752 return server_dict 1753 1754 1755def manage_server_command( 1756 server, 1757 name=None, 1758 inactive_is_error=False, 1759 disabled_is_error=True, 1760 skip_inactive=True, 1761 skip_disabled=True, 1762): 1763 """ 1764 Standard and consistent method for managing server errors within 1765 a server command execution. By default, suggests to skip any inactive 1766 and disabled server; it also emits errors for disabled servers by 1767 default. 1768 1769 Returns True if the command has to be executed for this server. 1770 1771 :param barman.server.Server server: server to be checked for errors 1772 :param str name: name of the server, in a multi-server command 1773 :param bool inactive_is_error: treat inactive server as error 1774 :param bool disabled_is_error: treat disabled server as error 1775 :param bool skip_inactive: skip if inactive 1776 :param bool skip_disabled: skip if disabled 1777 :return: True if the command has to be executed on this server 1778 :rtype: boolean 1779 """ 1780 1781 # Unknown server (skip it) 1782 if not server: 1783 output.error("Unknown server '%s'" % name) 1784 return False 1785 1786 if not server.config.active: 1787 # Report inactive server as error 1788 if inactive_is_error: 1789 output.error("Inactive server: %s" % server.config.name) 1790 if skip_inactive: 1791 return False 1792 1793 # Report disabled server as error 1794 if server.config.disabled: 1795 # Output all the messages as errors, and exit terminating the run. 1796 if disabled_is_error: 1797 for message in server.config.msg_list: 1798 output.error(message) 1799 if skip_disabled: 1800 return False 1801 1802 # All ok, execute the command 1803 return True 1804 1805 1806def parse_backup_id(server, args): 1807 """ 1808 Parses backup IDs including special words such as latest, oldest, etc. 1809 1810 Exit with error if the backup id doesn't exist. 1811 1812 :param Server server: server object to search for the required backup 1813 :param args: command line arguments namespace 1814 :rtype: barman.infofile.LocalBackupInfo 1815 """ 1816 if args.backup_id in ("latest", "last"): 1817 backup_id = server.get_last_backup_id() 1818 elif args.backup_id in ("oldest", "first"): 1819 backup_id = server.get_first_backup_id() 1820 elif args.backup_id in ("last-failed"): 1821 backup_id = server.get_last_backup_id([BackupInfo.FAILED]) 1822 else: 1823 backup_id = args.backup_id 1824 backup_info = server.get_backup(backup_id) 1825 if backup_info is None: 1826 output.error( 1827 "Unknown backup '%s' for server '%s'", args.backup_id, server.config.name 1828 ) 1829 output.close_and_exit() 1830 return backup_info 1831 1832 1833def main(): 1834 """ 1835 The main method of Barman 1836 """ 1837 # noinspection PyBroadException 1838 try: 1839 argcomplete.autocomplete(p) 1840 args = p.parse_args() 1841 global_config(args) 1842 if args.command is None: 1843 p.print_help() 1844 else: 1845 args.func(args) 1846 except KeyboardInterrupt: 1847 msg = "Process interrupted by user (KeyboardInterrupt)" 1848 output.error(msg) 1849 except Exception as e: 1850 msg = "%s\nSee log file for more details." % e 1851 output.exception(msg) 1852 1853 # cleanup output API and exit honoring output.error_occurred and 1854 # output.error_exit_code 1855 output.close_and_exit() 1856 1857 1858if __name__ == "__main__": 1859 # This code requires the mock module and allow us to test 1860 # bash completion inside the IDE debugger 1861 try: 1862 # noinspection PyUnresolvedReferences 1863 import mock 1864 1865 sys.stdout = mock.Mock(wraps=sys.stdout) 1866 sys.stdout.isatty.return_value = True 1867 os.dup2(2, 8) 1868 except ImportError: 1869 pass 1870 main() 1871