def dsn(cluster_name: str, group: Optional[int], role: Optional[CtlPostgresqlRole], member: Optional[str]) -> None:
    """Process ``dsn`` command of ``patronictl`` utility.

    Get DSN to connect to *member*.

    .. note::
        If no *role* nor *member* is given assume *role* as ``leader``.

    :param cluster_name: name of the Patroni cluster.
    :param group: filter which Citus group we should get members to get DSN from. Refer to the module note for more
        details.
    :param role: filter which members to get DSN from based on their role. See :func:`get_all_members` for available
        options.
    :param member: filter which member to get DSN from based on its name.

    :raises:
        :class:`PatroniCtlException`: if
            * both *role* and *member* are provided; or
            * No member matches requested *member* or *role*.
    """
    cluster = get_dcs(cluster_name, group).get_cluster()
    m = get_any_member(cluster, group, role=role, member=member)
    if m is None:
        raise PatroniCtlException('Can not find a suitable member')

    params = m.conn_kwargs()
    click.echo('host={host} port={port}'.format(**params))
Process ``dsn`` command of ``patronictl`` utility.

    Get DSN to connect to *member*.

    .. note::
        If no *role* nor *member* is given assume *role* as ``leader``.

    :param cluster_name: name of the Patroni cluster.
    :param group: filter which Citus group we should get members to get DSN from. Refer to the module note for more
        details.
    :param role: filter which members to get DSN from based on their role. See :func:`get_all_members` for available
        options.
    :param member: filter which member to get DSN from based on its name.

    :raises:
        :class:`PatroniCtlException`: if
            * both *role* and *member* are provided; or
            * No member matches requested *member* or *role*.
    
patroni/ctl.py