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