def output_members(cluster: Cluster, name: str, extended: bool = False,
                   fmt: str = 'pretty', group: Optional[int] = None) -> None:
    """Print information about the Patroni cluster and its members.

    Information is printed to console through :func:`print_output`, and contains:

        * ``Cluster``: name of the Patroni cluster, as per ``scope`` configuration;
        * ``Member``: name of the Patroni node, as per ``name`` configuration;
        * ``Host``: hostname (or IP) and port, as per ``postgresql.listen`` configuration;
        * ``Role``: ``Leader``, ``Standby Leader``, ``Sync Standby`` or ``Replica``;
        * ``State``: one of :class:`~patroni.postgresql.misc.PostgresqlState`;
        * ``TL``: current timeline in Postgres;
        * ``Receive LSN``: last received LSN (``pg_catalog.pg_last_(xlog|wal)_receive_(location|lsn)()``);
        * ``Receive Lag``: lag of the receive LSN in MB;
        * ``Replay LSN``: last replayed LSN (``pg_catalog.pg_last_(xlog|wal)_replay_(location|lsn)()``);
        * ``Replay Lag``: lag of the replay LSN in MB.

    Besides that it may also have:
        * ``Group``: Citus group ID -- showed only if Citus is enabled.
        * ``Pending restart``: if the node is pending a restart -- showed only if *extended*;
        * ``Scheduled restart``: timestamp for scheduled restart, if any -- showed only if *extended*;
        * ``Tags``: node tags, if any -- showed only if *extended*.

    The 3 extended columns are always included if *extended*, even if the member has no value for a given column.
    If not *extended*, these columns may still be shown if any of the members has any information for them.

    :param cluster: Patroni cluster.
    :param name: name of the Patroni cluster.
    :param extended: if extended information (pending restarts, scheduled restarts, node tags) should be printed, if
        available.
    :param fmt: the output table printing format. See :func:`print_output` for available options. If *fmt* is neither
        ``topology`` nor ``pretty``, then complementary information gathered through :func:`get_cluster_service_info` is
        not printed.
    :param group: filter which Citus group we should get members from. If ``None`` get from all groups.
    """
    rows: List[List[Any]] = []
    logging.debug(cluster)

    initialize = {None: 'uninitialized', '': 'initializing'}.get(cluster.initialize, cluster.initialize)
    columns = ['Cluster', 'Member', 'Host', 'Role', 'State', 'TL',
               'Receive LSN', 'Receive Lag', 'Replay LSN', 'Replay Lag']

    clusters = {group or 0: cluster_as_json(cluster)}

    if is_citus_cluster():
        columns.insert(1, 'Group')
        if group is None:
            clusters.update({g: cluster_as_json(c) for g, c in cluster.workers.items()})

    all_members = [m for c in clusters.values() for m in c['members'] if 'host' in m]

    for c in ('Pending restart', 'Pending restart reason', 'Scheduled restart', 'Tags'):
        if extended or any(m.get(c.lower().replace(' ', '_')) for m in all_members):
            columns.append(c)

    # Show Host as 'host:port' if somebody is running on non-standard port or two nodes are running on the same host
    append_port = any('port' in m and m['port'] != 5432 for m in all_members) or\
        len(set(m['host'] for m in all_members)) < len(all_members)

    sort = topology_sort if fmt == 'topology' else iter
    for g, c in sorted(clusters.items()):
        for member in sort(c['members']):
            logging.debug(member)

            def format_diff(param: str, values: Dict[str, str], hide_long: bool):
                full_diff = param + ': ' + values['old_value'] + '->' + values['new_value']
                return full_diff if not hide_long or len(full_diff) <= 50 else param + ': [hidden - too long]'
            restart_reason = '\n'.join([format_diff(k, v, fmt in ('pretty', 'topology'))
                                        for k, v in member.get('pending_restart_reason', {}).items()]) or ''

            receive_lag, replay_lag = member.get('receive_lag', ''), member.get('replay_lag', '')
            receive_lsn, replay_lsn = member.get('receive_lsn', ''), member.get('replay_lsn', '')
            lsn = member.get('lsn', '')
            # old lsn/lag implementation compatibility
            if 'unknown' == receive_lsn and replay_lsn == 'unknown' and lsn and lsn != 'unknown':
                lag = member.get('lag', '')
                if member['state'] == 'streaming':
                    receive_lag, receive_lsn = lag, lsn
                else:
                    replay_lag, replay_lsn = lag, lsn
            receive_lag = round(receive_lag / 1024 / 1024) if isinstance(receive_lag, int) \
                else '' if receive_lag == 'unknown' else receive_lag
            replay_lag = round(replay_lag / 1024 / 1024) if isinstance(replay_lag, int) \
                else '' if replay_lag == 'unknown' else replay_lag

            member.update(cluster=name, member=member['name'], group=g,
                          host=member.get('host', ''), tl=member.get('timeline', ''),
                          role=member['role'].replace('_', ' ').title(),
                          receive_lag=receive_lag, replay_lag=replay_lag,
                          receive_lsn=receive_lsn, replay_lsn=replay_lsn,
                          pending_restart='*' if member.get('pending_restart') else '',
                          pending_restart_reason=restart_reason)

            if append_port and member['host'] and member.get('port'):
                member['host'] = ':'.join([member['host'], str(member['port'])])

            if 'scheduled_restart' in member:
                value = member['scheduled_restart']['schedule']
                if 'postgres_version' in member['scheduled_restart']:
                    value += ' if version < {0}'.format(member['scheduled_restart']['postgres_version'])
                member['scheduled_restart'] = value

            rows.append([member.get(n.lower().replace(' ', '_'), '') for n in columns])

    if is_citus_cluster():
        title = 'Citus cluster'
        title_details = '' if group is None else f' (group: {group}, {initialize})'
    else:
        title = 'Cluster'
        title_details = f' ({initialize})'

    title = f' {title}: {name}{title_details} '
    if fmt in ('pretty', 'topology'):
        columns[columns.index('Replay Lag')] = columns[columns.index('Receive Lag')] = 'Lag'
    print_output(columns, rows,
                 {'Group': 'r', 'Receive LSN': 'r', 'Replay LSN': 'r', 'Lag': 'r', 'TL': 'r'}, fmt, title)

    if fmt not in ('pretty', 'topology'):  # Omit service info when using machine-readable formats
        return

    for g, c in sorted(clusters.items()):
        service_info = get_cluster_service_info(c)
        if service_info:
            if is_citus_cluster() and group is None:
                click.echo('Citus group: {0}'.format(g))
            click.echo(' ' + '\n '.join(service_info))
Print information about the Patroni cluster and its members.

    Information is printed to console through :func:`print_output`, and contains:

        * ``Cluster``: name of the Patroni cluster, as per ``scope`` configuration;
        * ``Member``: name of the Patroni node, as per ``name`` configuration;
        * ``Host``: hostname (or IP) and port, as per ``postgresql.listen`` configuration;
        * ``Role``: ``Leader``, ``Standby Leader``, ``Sync Standby`` or ``Replica``;
        * ``State``: one of :class:`~patroni.postgresql.misc.PostgresqlState`;
        * ``TL``: current timeline in Postgres;
        * ``Receive LSN``: last received LSN (``pg_catalog.pg_last_(xlog|wal)_receive_(location|lsn)()``);
        * ``Receive Lag``: lag of the receive LSN in MB;
        * ``Replay LSN``: last replayed LSN (``pg_catalog.pg_last_(xlog|wal)_replay_(location|lsn)()``);
        * ``Replay Lag``: lag of the replay LSN in MB.

    Besides that it may also have:
        * ``Group``: Citus group ID -- showed only if Citus is enabled.
        * ``Pending restart``: if the node is pending a restart -- showed only if *extended*;
        * ``Scheduled restart``: timestamp for scheduled restart, if any -- showed only if *extended*;
        * ``Tags``: node tags, if any -- showed only if *extended*.

    The 3 extended columns are always included if *extended*, even if the member has no value for a given column.
    If not *extended*, these columns may still be shown if any of the members has any information for them.

    :param cluster: Patroni cluster.
    :param name: name of the Patroni cluster.
    :param extended: if extended information (pending restarts, scheduled restarts, node tags) should be printed, if
        available.
    :param fmt: the output table printing format. See :func:`print_output` for available options. If *fmt* is neither
        ``topology`` nor ``pretty``, then complementary information gathered through :func:`get_cluster_service_info` is
        not printed.
    :param group: filter which Citus group we should get members from. If ``None`` get from all groups.
    
patroni/ctl.py