Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package crmsh for openSUSE:Factory checked 
in at 2024-04-02 16:42:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/crmsh (Old)
 and      /work/SRC/openSUSE:Factory/.crmsh.new.1905 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "crmsh"

Tue Apr  2 16:42:37 2024 rev:327 rq:1163701 version:4.6.0+20240330.3473a5ba

Changes:
--------
--- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes      2024-03-13 
22:22:26.700827362 +0100
+++ /work/SRC/openSUSE:Factory/.crmsh.new.1905/crmsh.changes    2024-04-02 
16:44:11.916979724 +0200
@@ -1,0 +2,31 @@
+Sat Mar 30 12:13:23 UTC 2024 - [email protected]
+
+- Update to version 4.6.0+20240330.3473a5ba:
+  * Dev: behave: Addd functional test for previous commit
+  * Dev: bootstrap: Give a warning when detecting $SSH_AUTH_SOCK but not use 
--use-ssh-agent option
+
+-------------------------------------------------------------------
+Wed Mar 27 08:04:12 UTC 2024 - [email protected]
+
+- Update to version 4.6.0+20240327.74a899fc:
+  * Dev: ui_context: make help subcommands to exit with 0 (#1374)
+
+-------------------------------------------------------------------
+Mon Mar 25 08:29:51 UTC 2024 - [email protected]
+
+- Update to version 4.6.0+20240325.bb913aba:
+  * Dev: behave: Adjust functional test for previous commit
+  * Fix: sh: Return the value of AuthorizationError.diagnose if it is not None
+
+-------------------------------------------------------------------
+Fri Mar 15 07:03:07 UTC 2024 - [email protected]
+
+- Update to version 4.6.0+20240315.053594ab:
+  * Dev: unittest: Adjust unit test for previous commit
+  * Dev: bootstrap: Remove unused ssh_remote related code
+  * Dev: unittest: Adjust unit test for previous commit
+  * Dev: bootstrap: Remove unused function bootstrap.append_unique
+  * Dev: ssh_key: Extract duplicate code about list keys from ssh-agent to a 
function
+  * Dev: bootstrap: Refactor qdevice user parsing and finding
+
+-------------------------------------------------------------------

Old:
----
  crmsh-4.6.0+20240313.8278d949.tar.bz2

New:
----
  crmsh-4.6.0+20240330.3473a5ba.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ crmsh.spec ++++++
--- /var/tmp/diff_new_pack.hChqCJ/_old  2024-04-02 16:44:13.057020418 +0200
+++ /var/tmp/diff_new_pack.hChqCJ/_new  2024-04-02 16:44:13.061020561 +0200
@@ -36,7 +36,7 @@
 Summary:        High Availability cluster command-line interface
 License:        GPL-2.0-or-later
 Group:          %{pkg_group}
-Version:        4.6.0+20240313.8278d949
+Version:        4.6.0+20240330.3473a5ba
 Release:        0
 URL:            http://crmsh.github.io
 Source0:        %{name}-%{version}.tar.bz2

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.hChqCJ/_old  2024-04-02 16:44:13.101021989 +0200
+++ /var/tmp/diff_new_pack.hChqCJ/_new  2024-04-02 16:44:13.101021989 +0200
@@ -9,7 +9,7 @@
 </service>
 <service name="tar_scm">
   <param name="url">https://github.com/ClusterLabs/crmsh.git</param>
-  <param 
name="changesrevision">8278d9498f904df34feebd387225eecd2cca21f4</param>
+  <param 
name="changesrevision">3473a5ba98249197c99ba63dd2a32b675493d6f9</param>
 </service>
 </servicedata>
 (No newline at EOF)

++++++ crmsh-4.6.0+20240313.8278d949.tar.bz2 -> 
crmsh-4.6.0+20240330.3473a5ba.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.6.0+20240313.8278d949/.github/workflows/crmsh-ci.yml 
new/crmsh-4.6.0+20240330.3473a5ba/.github/workflows/crmsh-ci.yml
--- old/crmsh-4.6.0+20240313.8278d949/.github/workflows/crmsh-ci.yml    
2024-03-13 14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/.github/workflows/crmsh-ci.yml    
2024-03-30 12:48:39.000000000 +0100
@@ -351,7 +351,7 @@
     timeout-minutes: 40
     steps:
     - uses: actions/checkout@v3
-    - name: functional test for user access
+    - name: functional test for ssh agent
       run:  |
         echo '{ "exec-opts": ["native.cgroupdriver=systemd"] }' | sudo tee 
/etc/docker/daemon.json
         sudo systemctl restart docker.service
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.6.0+20240313.8278d949/crmsh/bootstrap.py 
new/crmsh-4.6.0+20240330.3473a5ba/crmsh/bootstrap.py
--- old/crmsh-4.6.0+20240313.8278d949/crmsh/bootstrap.py        2024-03-13 
14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/crmsh/bootstrap.py        2024-03-30 
12:48:39.000000000 +0100
@@ -74,7 +74,7 @@
         "/etc/drbd.conf", "/etc/drbd.d", "/etc/ha.d/ldirectord.cf", 
"/etc/lvm/lvm.conf", "/etc/multipath.conf",
         "/etc/samba/smb.conf", SYSCONFIG_NFS, SYSCONFIG_PCMK, SYSCONFIG_SBD, 
PCMK_REMOTE_AUTH, WATCHDOG_CFG,
         PROFILES_FILE, CRM_CFG, SBD_SYSTEMD_DELAY_START_DIR)
-INIT_STAGES = ("ssh", "ssh_remote", "csync2", "csync2_remote", "corosync", 
"remote_auth", "sbd", "cluster", "ocfs2", "admin", "qdevice")
+INIT_STAGES = ("ssh", "csync2", "csync2_remote", "corosync", "remote_auth", 
"sbd", "cluster", "ocfs2", "admin", "qdevice")
 
 
 class Context(object):
@@ -105,7 +105,7 @@
         self.second_heartbeat = None
         self.ipv6 = None
         self.qdevice_inst = None
-        self.qnetd_addr = None
+        self.qnetd_addr_input = None
         self.qdevice_port = None
         self.qdevice_algo = None
         self.qdevice_tie_breaker = None
@@ -143,7 +143,7 @@
                 COROSYNC_AUTH, "/var/lib/heartbeat/crm/*", 
"/var/lib/pacemaker/cib/*",
                 "/var/lib/corosync/*", "/var/lib/pacemaker/pengine/*", 
PCMK_REMOTE_AUTH,
                 "/var/lib/csync2/*", "~/.config/crm/*"]
-        self.use_ssh_agent = False
+        self.use_ssh_agent = None
 
     @classmethod
     def set_context(cls, options):
@@ -157,17 +157,11 @@
         """
         Initialize qdevice instance
         """
-        if not self.qnetd_addr:
+        if not self.qnetd_addr_input:
             return
-        parts = self.qnetd_addr.split('@', 2)
-        if len(parts) == 2:
-            ssh_user = parts[0]
-            qnetd_host = parts[1]
-        else:
-            ssh_user = None
-            qnetd_host = self.qnetd_addr
+        ssh_user, qnetd_host = utils.parse_user_at_host(self.qnetd_addr_input)
         self.qdevice_inst = qdevice.QDevice(
-                qnetd_host,
+                qnetd_addr=qnetd_host,
                 port=self.qdevice_port,
                 algo=self.qdevice_algo,
                 tie_breaker=self.qdevice_tie_breaker,
@@ -583,6 +577,11 @@
         logger.warning("{} is not configured to start at system 
boot.".format(timekeeper))
         warned = True
 
+    if _context.use_ssh_agent == False and 'SSH_AUTH_SOCK' in os.environ:
+        msg = "$SSH_AUTH_SOCK is detected. As a tip, using the --use-ssh-agent 
option could avoid generate local root ssh keys on cluster nodes."
+        logger.warning(msg)
+        warned = True
+
     if warned:
         if not confirm("Do you want to continue anyway?"):
             return False
@@ -792,29 +791,6 @@
     return service_manager.start_service("pacemaker.service", 
enable=enable_flag, node_list=node_list)
 
 
-def append(fromfile, tofile, remote=None):
-    cmd = "cat {} >> {}".format(fromfile, tofile)
-    sh.cluster_shell().get_stdout_or_raise_error(cmd, host=remote)
-
-
-def append_unique(fromfile, tofile, user=None, remote=None, from_local=False):
-    """
-    Append unique content from fromfile to tofile
-    
-    if from_local and remote:
-        append local fromfile to remote tofile
-    elif remote:
-        append remote fromfile to remote tofile
-    if not remote:
-        append fromfile to tofile, locally
-    """
-    if not utils.check_file_content_included(fromfile, tofile, remote=remote, 
source_local=from_local):
-        if from_local and remote:
-            append_to_remote_file(fromfile, user, remote, tofile)
-        else:
-            append(fromfile, tofile, remote=remote)
-
-
 def _parse_user_at_host(s: str, default_user: str) -> typing.Tuple[str, str]:
     user, host = utils.parse_user_at_host(s)
     if user is None:
@@ -822,18 +798,19 @@
     return user, host
 
 
+def _keys_from_ssh_agent() -> typing.List[ssh_key.Key]:
+    try:
+        keys = ssh_key.AgentClient().list()
+        logger.info("Using public keys from ssh-agent...")
+    except Error:
+        logger.error("Cannot get a public key from ssh-agent.")
+        raise
+    return keys
+
+
 def init_ssh():
     user_host_list = [_parse_user_at_host(x, _context.current_user) for x in 
_context.user_at_node_list]
-    if _context.use_ssh_agent:
-        try:
-            ssh_agent = ssh_key.AgentClient()
-            keys = ssh_agent.list()
-            logger.info("Using public keys from ssh-agent...")
-        except ssh_key.Error:
-            logger.error("Cannot get a public key from ssh-agent.")
-            raise
-    else:
-        keys = list()
+    keys = _keys_from_ssh_agent() if _context.use_ssh_agent else list()
     init_ssh_impl(_context.current_user, keys, user_host_list)
     if user_host_list:
         service_manager = ServiceManager()
@@ -1074,28 +1051,6 @@
     return result.stdout.decode('utf-8').strip()
 
 
-def init_ssh_remote():
-    """
-    Called by ha-cluster-join
-    """
-    user = userdir.get_sudoer()
-    if user is None:
-        user = userdir.getuser()
-    _, _, authorized_keys_file = key_files(user).values()
-    if not os.path.exists(authorized_keys_file):
-        open(authorized_keys_file, 'w').close()
-    authkeys = open(authorized_keys_file, "r+")
-    authkeys_data = authkeys.read()
-    dirname = os.path.dirname(authorized_keys_file)
-    for key in ("id_rsa", "id_dsa", "id_ecdsa", "id_ed25519"):
-        fn = os.path.join(dirname, key)
-        if not os.path.exists(fn):
-            continue
-        keydata = open(fn + ".pub").read()
-        if keydata not in authkeys_data:
-            append(fn + ".pub", authorized_keys_file)
-
-
 def export_ssh_key_non_interactive(local_user_to_export, remote_user_to_swap, 
remote_node, local_sudoer, remote_sudoer):
     """Copy ssh key from local to remote's authorized_keys. Require a 
configured non-interactive ssh authentication."""
     # ssh-copy-id will prompt for the password of the destination user
@@ -1131,13 +1086,6 @@
             "sed -i '$a {}' '{}'".format(remote_key_content, 
local_authorized_file),
         )
 
-def append_to_remote_file(fromfile, user, remote_node, tofile):
-    """
-    Append content of fromfile to tofile on remote_node
-    """
-    cmd = "cat {} | ssh {} {}@{} 'cat >> {}'".format(fromfile, SSH_OPTION, 
user, remote_node, tofile)
-    sh.cluster_shell().get_stdout_or_raise_error(cmd)
-
 
 def init_csync2():
     host_list = _context.node_list_in_cluster
@@ -1592,8 +1540,10 @@
             else:
                 return
 
-    qnetd_addr = prompt_for_string("HOST or IP of the QNetd server to be used",
-            valid_func=qdevice.QDevice.check_qnetd_addr)
+    qnetd_addr_input = prompt_for_string("HOST or IP of the QNetd server to be 
used")
+    ssh_user, qnetd_host = utils.parse_user_at_host(qnetd_addr_input)
+    qdevice.QDevice.check_qnetd_addr(qnetd_host)
+    _context.qnetd_addr_input = qnetd_addr_input
     qdevice_port = prompt_for_string("TCP PORT of QNetd server", default=5403,
             valid_func=qdevice.QDevice.check_qdevice_port)
     qdevice_algo = prompt_for_string("QNetd decision ALGORITHM (ffsplit/lms)", 
default="ffsplit",
@@ -1608,14 +1558,6 @@
     qdevice_heuristics_mode = prompt_for_string("MODE of operation of 
heuristics (on/sync/off)", default="sync",
             valid_func=qdevice.QDevice.check_qdevice_heuristics_mode) if 
qdevice_heuristics else None
 
-    parts = qnetd_addr.split('@', 2)
-    if len(parts) == 2:
-        ssh_user = parts[0]
-        qnetd_host = parts[1]
-    else:
-        ssh_user = None
-        qnetd_host = qnetd_addr
-
     _context.qdevice_inst = qdevice.QDevice(
             qnetd_host,
             port=qdevice_port,
@@ -1644,28 +1586,7 @@
         if not 
ServiceManager().service_is_available("corosync-qdevice.service", node):
             utils.fatal("corosync-qdevice.service is not available on 
{}".format(node))
     qdevice_inst = _context.qdevice_inst
-    qnetd_addr = qdevice_inst.qnetd_addr
-    local_user = None
-    ssh_user = None
-    if qdevice_inst.ssh_user is not None:
-        # if the remote user is specified explicitly, use it
-        ssh_user = qdevice_inst.ssh_user
-        try:
-            local_user = UserOfHost.instance().user_of(utils.this_node())
-        except UserNotFoundError:
-            local_user = ssh_user
-    else:
-        try:
-            # if ssh session has ready been available, use that
-            local_user, ssh_user = 
UserOfHost.instance().user_pair_for_ssh(qnetd_addr)
-        except UserNotFoundError:
-            pass
-    if ssh_user is None:
-        try:
-            local_user = UserOfHost.instance().user_of(utils.this_node())
-        except UserNotFoundError:
-            local_user = userdir.getuser()
-        ssh_user = local_user
+    local_user, ssh_user, qnetd_addr = 
_select_user_pair_for_ssh_for_secondary_components(_context.qnetd_addr_input)
     # Configure ssh passwordless to qnetd if detect password is needed
     if UserOfHost.instance().use_ssh_agent():
         logger.info("Adding public keys to authorized_keys for user root...")
@@ -1723,17 +1644,7 @@
     if not seed_host:
         utils.fatal("No existing IP/hostname specified (use -c option)")
     local_user = _context.current_user
-
-    if _context.use_ssh_agent:
-        try:
-            ssh_agent = ssh_key.AgentClient()
-            keys = ssh_agent.list()
-            logger.info("Using public keys from ssh-agent...")
-        except ssh_key.Error:
-            logger.error("Cannot get a public key from ssh-agent.")
-            raise
-    else:
-        keys = list()
+    keys = _keys_from_ssh_agent() if _context.use_ssh_agent else list()
     return join_ssh_impl(local_user, seed_host, seed_user, keys)
 
 
@@ -1760,17 +1671,6 @@
         # After this, login to remote_node is passwordless
         swap_public_ssh_key(seed_host, local_user, seed_user, local_user, 
seed_user, add=True)
 
-    # This makes sure the seed host has its own SSH keys in its own
-    # authorized_keys file (again, to help with the case where the
-    # user has done manual initial setup without the assistance of
-    # ha-cluster-init).
-    if not ssh_public_keys:
-        local_shell.get_stdout_or_raise_error(
-            local_user,
-            "ssh {} {}@{} sudo crm cluster init ssh_remote".format(
-                SSH_OPTION, seed_user, seed_host
-            ),
-        )
     user_by_host = utils.HostUserConfig()
     user_by_host.clear()
     user_by_host.add(seed_user, seed_host)
@@ -2457,15 +2357,15 @@
     elif stage == "":
         if _context.cluster_is_running:
             utils.fatal("Cluster is currently active - can't run")
-    elif stage not in ("ssh", "ssh_remote", "csync2", "csync2_remote", "sbd", 
"ocfs2"):
+    elif stage not in ("ssh", "csync2", "csync2_remote", "sbd", "ocfs2"):
         if _context.cluster_is_running:
             utils.fatal("Cluster is currently active - can't run %s stage" % 
(stage))
 
     _context.load_profiles()
     _context.init_sbd_manager()
 
-    # Need hostname resolution to work, want NTP (but don't block ssh_remote 
or csync2_remote)
-    if stage not in ('ssh_remote', 'csync2_remote'):
+    # Need hostname resolution to work, want NTP (but don't block 
csync2_remote)
+    if stage not in ('csync2_remote',):
         check_tty()
         if not check_prereqs(stage):
             return
@@ -2909,13 +2809,7 @@
     if not sh.cluster_shell().can_run_as(node, 'root'):
         local_user, remote_user, node = 
_select_user_pair_for_ssh_for_secondary_components(_context.cluster_node)
         if context.use_ssh_agent:
-            try:
-                ssh_agent = ssh_key.AgentClient()
-                keys = ssh_agent.list()
-                logger.info("Using public keys from ssh-agent...")
-            except ssh_key.Error:
-                logger.error("Cannot get a public key from ssh-agent.")
-                raise
+            keys = _keys_from_ssh_agent()
             local_shell = sh.LocalShell(additional_environ={'SSH_AUTH_SOCK': 
os.environ.get('SSH_AUTH_SOCK')})
             join_ssh_with_ssh_agent(local_shell, local_user, node, 
remote_user, keys)
         else:
@@ -2951,13 +2845,7 @@
     if not sh.cluster_shell().can_run_as(node, 'root'):
         local_user, remote_user, node = 
_select_user_pair_for_ssh_for_secondary_components(_context.cluster_node)
         if context.use_ssh_agent:
-            try:
-                ssh_agent = ssh_key.AgentClient()
-                keys = ssh_agent.list()
-                logger.info("Using public keys from ssh-agent...")
-            except ssh_key.Error:
-                logger.error("Cannot get a public key from ssh-agent.")
-                raise
+            keys = _keys_from_ssh_agent()
             local_shell = sh.LocalShell(additional_environ={'SSH_AUTH_SOCK': 
os.environ.get('SSH_AUTH_SOCK')})
             join_ssh_with_ssh_agent(local_shell, local_user, node, 
remote_user, keys)
         else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.6.0+20240313.8278d949/crmsh/crash_test/main.py 
new/crmsh-4.6.0+20240330.3473a5ba/crmsh/crash_test/main.py
--- old/crmsh-4.6.0+20240313.8278d949/crmsh/crash_test/main.py  2024-03-13 
14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/crmsh/crash_test/main.py  2024-03-30 
12:48:39.000000000 +0100
@@ -168,7 +168,7 @@
     args = parser.parse_args()
     if args.help or len(sys.argv) == 1:
         parser.print_help()
-        raise crmshutils.TerminateSubCommand
+        raise crmshutils.TerminateSubCommand(success=True)
 
     for arg in vars(args):
         setattr(context, arg, getattr(args, arg))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.6.0+20240313.8278d949/crmsh/sh.py 
new/crmsh-4.6.0+20240330.3473a5ba/crmsh/sh.py
--- old/crmsh-4.6.0+20240313.8278d949/crmsh/sh.py       2024-03-13 
14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/crmsh/sh.py       2024-03-30 
12:48:39.000000000 +0100
@@ -72,6 +72,7 @@
         ret = super().diagnose()
         if not ret:
             return 'Please configure passwordless authentication with "crm 
cluster init ssh" and "crm cluster join ssh"'
+        return ret
 
 
 class CommandFailure(Error):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.6.0+20240313.8278d949/crmsh/ui_cluster.py 
new/crmsh-4.6.0+20240330.3473a5ba/crmsh/ui_cluster.py
--- old/crmsh-4.6.0+20240313.8278d949/crmsh/ui_cluster.py       2024-03-13 
14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/crmsh/ui_cluster.py       2024-03-30 
12:48:39.000000000 +0100
@@ -25,17 +25,19 @@
 
 
 from . import log
+from .utils import TerminateSubCommand
+
 logger = log.setup_logger(__name__)
 
 
 def parse_options(parser, args):
     try:
         options, args = parser.parse_known_args(list(args))
-    except:
+    except Exception:
         return None, None
     if hasattr(options, 'help') and options.help:
         parser.print_help()
-        return None, None
+        raise TerminateSubCommand(success=True)
     utils.check_empty_option_value(options)
     return options, args
 
@@ -434,7 +436,7 @@
                                    help="Configure corosync use IPv6")
 
         qdevice_group = parser.add_argument_group("QDevice configuration", 
re.sub('  ', '', constants.QDEVICE_HELP_INFO) + "\n\nOptions for configuring 
QDevice and QNetd.")
-        qdevice_group.add_argument("--qnetd-hostname", dest="qnetd_addr", 
metavar="[USER@]HOST",
+        qdevice_group.add_argument("--qnetd-hostname", 
dest="qnetd_addr_input", metavar="[USER@]HOST",
                                    help="User and host of the QNetd server. 
The host can be specified in either hostname or IP address.")
         qdevice_group.add_argument("--qdevice-port", dest="qdevice_port", 
metavar="PORT", type=int, default=5403,
                                    help="TCP PORT of QNetd server 
(default:5403)")
@@ -472,7 +474,7 @@
         if stage not in bootstrap.INIT_STAGES and stage != "":
             parser.error("Invalid stage (%s)" % (stage))
 
-        if options.qnetd_addr:
+        if options.qnetd_addr_input:
             if not 
ServiceManager().service_is_available("corosync-qdevice.service"):
                 utils.fatal("corosync-qdevice.service is not available")
             if options.qdevice_heuristics_mode and not 
options.qdevice_heuristics:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.6.0+20240313.8278d949/crmsh/ui_context.py 
new/crmsh-4.6.0+20240330.3473a5ba/crmsh/ui_context.py
--- old/crmsh-4.6.0+20240313.8278d949/crmsh/ui_context.py       2024-03-13 
14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/crmsh/ui_context.py       2024-03-30 
12:48:39.000000000 +0100
@@ -87,8 +87,11 @@
         except (ValueError, IOError) as e:
             logger.error("%s: %s", self.get_qualified_name(), e, exc_info=e)
             rv = False
-        except utils.TerminateSubCommand:
-            return False
+        except utils.TerminateSubCommand as terminate:
+            if terminate.success:
+                rv = True
+            else:
+                return False
         if cmd or (rv is False):
             rv = self._back_out() and rv
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.6.0+20240313.8278d949/crmsh/ui_node.py 
new/crmsh-4.6.0+20240330.3473a5ba/crmsh/ui_node.py
--- old/crmsh-4.6.0+20240313.8278d949/crmsh/ui_node.py  2024-03-13 
14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/crmsh/ui_node.py  2024-03-30 
12:48:39.000000000 +0100
@@ -237,7 +237,7 @@
     options, args = parser.parse_known_args(args)
     if options.help:
         parser.print_help()
-        raise utils.TerminateSubCommand
+        raise utils.TerminateSubCommand(success=True)
     if options is None or args is None:
         raise utils.TerminateSubCommand
     if options.all and args:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.6.0+20240313.8278d949/crmsh/utils.py 
new/crmsh-4.6.0+20240330.3473a5ba/crmsh/utils.py
--- old/crmsh-4.6.0+20240313.8278d949/crmsh/utils.py    2024-03-13 
14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/crmsh/utils.py    2024-03-30 
12:48:39.000000000 +0100
@@ -52,6 +52,8 @@
     """
     This is an exception to jump out of subcommand when meeting errors while 
staying interactive shell
     """
+    def __init__(self, success=False):
+        self.success = success
 
 
 def to_ascii(input_str):
@@ -2496,22 +2498,6 @@
         return _ip_list
 
 
-def check_file_content_included(source_file, target_file, remote=None, 
source_local=False):
-    """
-    Check whether target_file includes contents of source_file
-    """
-    if not detect_file(source_file, remote=None if source_local else remote):
-        raise ValueError("File {} not exist".format(source_file))
-    if not detect_file(target_file, remote=remote):
-        return False
-
-    shell = sh.cluster_shell()
-    cmd = "cat {}".format(target_file)
-    target_data = shell.get_stdout_or_raise_error(cmd, host=remote)
-    cmd = "cat {}".format(source_file)
-    source_data = shell.get_stdout_or_raise_error(cmd, host=None if 
source_local else remote)
-    return source_data in target_data
-
 def check_text_included(text, target_file, remote=None):
     "Check whether target_file includes the text"
     if not detect_file(target_file, remote=remote):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.6.0+20240313.8278d949/test/features/ssh_agent.feature 
new/crmsh-4.6.0+20240330.3473a5ba/test/features/ssh_agent.feature
--- old/crmsh-4.6.0+20240313.8278d949/test/features/ssh_agent.feature   
2024-03-13 14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/test/features/ssh_agent.feature   
2024-03-30 12:48:39.000000000 +0100
@@ -30,6 +30,17 @@
     And     Run "test x3 == x$(sudo awk 'END {print NR}' 
~hacluster/.ssh/authorized_keys)" OK
     And     Run "grep -E 'hosts = (root|alice)@hanode1' 
/root/.config/crm/crm.conf" OK on "hanode1,hanode2,hanode3"
 
+  # This test is not applicable for non-root user, since the root ssh key pair 
exists
+  @skip_non_root
+  Scenario: Verify expected error message when SSH_AUTH_SOCK is not set
+    When    Try "crm cluster remove hanode3 -y" on "hanode1"
+    Then    Expected "Environment variable SSH_AUTH_SOCK does not exist" in 
stderr
+
+  Scenario: Give a warning when detected SSH_AUTH_SOCK but not using 
--use-ssh-agent
+    Given   Run "crm cluster stop" OK on "hanode1,hanode2,hanode3"
+    When    Try "SSH_AUTH_SOCK=/tmp/ssh-auth-sock crm cluster init -y" on 
"hanode1"
+    Then    Expected "$SSH_AUTH_SOCK is detected. As a tip, using the 
--use-ssh-agent option could avoid generate local root ssh keys on cluster 
nodes" in stderr
+
   Scenario: Skip creating ssh key pairs with --use-ssh-agent and use -N
     Given   Run "crm cluster stop" OK on "hanode1,hanode2,hanode3"
     When    Run "SSH_AUTH_SOCK=/tmp/ssh-auth-sock crm cluster init 
--use-ssh-agent -y -N hanode2 -N hanode3" on "hanode1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.6.0+20240313.8278d949/test/unittests/test_bootstrap.py 
new/crmsh-4.6.0+20240330.3473a5ba/test/unittests/test_bootstrap.py
--- old/crmsh-4.6.0+20240313.8278d949/test/unittests/test_bootstrap.py  
2024-03-13 14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/test/unittests/test_bootstrap.py  
2024-03-30 12:48:39.000000000 +0100
@@ -75,20 +75,20 @@
     @mock.patch('crmsh.qdevice.QDevice')
     def test_initialize_qdevice(self, mock_qdevice):
         ctx = crmsh.bootstrap.Context()
-        ctx.qnetd_addr = "node3"
+        ctx.qnetd_addr_input = "node3"
         ctx.qdevice_port = 123
         ctx.stage = ""
         ctx.initialize_qdevice()
-        mock_qdevice.assert_called_once_with('node3', port=123, ssh_user=None, 
algo=None, tie_breaker=None, tls=None, cmds=None, mode=None, is_stage=False)
+        mock_qdevice.assert_called_once_with(qnetd_addr='node3', port=123, 
ssh_user=None, algo=None, tie_breaker=None, tls=None, cmds=None, mode=None, 
is_stage=False)
 
     @mock.patch('crmsh.qdevice.QDevice')
     def test_initialize_qdevice_with_user(self, mock_qdevice):
         ctx = crmsh.bootstrap.Context()
-        ctx.qnetd_addr = "alice@node3"
+        ctx.qnetd_addr_input = "alice@node3"
         ctx.qdevice_port = 123
         ctx.stage = ""
         ctx.initialize_qdevice()
-        mock_qdevice.assert_called_once_with('node3', port=123, 
ssh_user='alice', algo=None, tie_breaker=None, tls=None, cmds=None, mode=None, 
is_stage=False)
+        mock_qdevice.assert_called_once_with(qnetd_addr='node3', port=123, 
ssh_user='alice', algo=None, tie_breaker=None, tls=None, cmds=None, mode=None, 
is_stage=False)
 
     @mock.patch('crmsh.utils.fatal')
     def test_validate_sbd_option_error_together(self, mock_error):
@@ -515,12 +515,11 @@
             ),
         ])
 
-    @mock.patch('crmsh.bootstrap.append_unique')
     @mock.patch('crmsh.sh.LocalShell.get_stdout_or_raise_error')
     @mock.patch('crmsh.utils.detect_file')
     @mock.patch('crmsh.bootstrap.key_files')
     @mock.patch('crmsh.bootstrap.change_user_shell')
-    def _test_configure_ssh_key(self, mock_change_shell, mock_key_files, 
mock_detect, mock_su, mock_append_unique):
+    def _test_configure_ssh_key(self, mock_change_shell, mock_key_files, 
mock_detect, mock_su):
         mock_key_files.return_value = {"private": "/test/.ssh/id_rsa", 
"public": "/test/.ssh/id_rsa.pub", "authorized": "/test/.ssh/authorized_keys"}
         mock_detect.side_effect = [True, True, False]
 
@@ -533,7 +532,6 @@
             mock.call("/test/.ssh/id_rsa.pub"),
             mock.call("/test/.ssh/authorized_keys")
             ])
-        mock_append_unique.assert_called_once_with("/test/.ssh/id_rsa.pub", 
"/test/.ssh/authorized_keys", "test")
         mock_su.assert_called_once_with('test', 'touch 
/test/.ssh/authorized_keys')
 
     @mock.patch('crmsh.ssh_key.AuthorizedKeyManager.add')
@@ -545,28 +543,6 @@
         mock_ensure_key_pair.assert_called_once_with(None, 'alice')
         mock_add.assert_called_once_with(None, 'alice', public_key)
 
-    @mock.patch('crmsh.bootstrap.append_to_remote_file')
-    @mock.patch('crmsh.utils.check_file_content_included')
-    def test_append_unique_remote(self, mock_check, mock_append):
-        mock_check.return_value = False
-        bootstrap.append_unique("fromfile", "tofile", user="root", 
remote="node1", from_local=True)
-        mock_check.assert_called_once_with("fromfile", "tofile", 
remote="node1", source_local=True)
-        mock_append.assert_called_once_with("fromfile", "root", "node1", 
"tofile")
-
-    @mock.patch('crmsh.bootstrap.append')
-    @mock.patch('crmsh.utils.check_file_content_included')
-    def test_append_unique(self, mock_check, mock_append):
-        mock_check.return_value = False
-        bootstrap.append_unique("fromfile", "tofile")
-        mock_check.assert_called_once_with("fromfile", "tofile", remote=None, 
source_local=False)
-        mock_append.assert_called_once_with("fromfile", "tofile", remote=None)
-
-    @mock.patch('crmsh.sh.ClusterShell.get_stdout_or_raise_error')
-    def test_append_to_remote_file(self, mock_run):
-        bootstrap.append_to_remote_file("fromfile", "root", "node1", "tofile")
-        cmd = "cat fromfile | ssh {} root@node1 'cat >> 
tofile'".format(constants.SSH_OPTION)
-        mock_run.assert_called_once_with(cmd)
-
     @mock.patch('crmsh.utils.fatal')
     def test_join_ssh_no_seed_host(self, mock_error):
         mock_error.side_effect = ValueError
@@ -577,18 +553,16 @@
     @mock.patch('crmsh.bootstrap.get_node_canonical_hostname')
     @mock.patch('crmsh.bootstrap.swap_public_ssh_key_for_secondary_user')
     @mock.patch('crmsh.bootstrap.change_user_shell')
-    @mock.patch('crmsh.sh.LocalShell.get_stdout_or_raise_error')
     @mock.patch('crmsh.bootstrap.swap_public_ssh_key')
     @mock.patch('crmsh.utils.ssh_copy_id_no_raise')
     @mock.patch('crmsh.bootstrap.configure_ssh_key')
     @mock.patch('crmsh.service_manager.ServiceManager.start_service')
     def test_join_ssh(
             self,
-            mock_start_service, mock_config_ssh, mock_ssh_copy_id, mock_swap, 
mock_invoke, mock_change, mock_swap_2,
+            mock_start_service, mock_config_ssh, mock_ssh_copy_id, mock_swap, 
mock_change, mock_swap_2,
             mock_get_node_cononical_hostname,
     ):
-        bootstrap._context = mock.Mock(current_user="bob", 
default_nic_list=["eth1"], use_ssh_agent=False)
-        mock_invoke.return_value = ''
+        bootstrap._context = mock.Mock(current_user="bob", default_nic="eth1", 
use_ssh_agent=False)
         mock_swap.return_value = None
         mock_ssh_copy_id.return_value = 0
         mock_get_node_cononical_hostname.return_value='node1'
@@ -602,10 +576,6 @@
             ])
         mock_ssh_copy_id.assert_called_once_with("bob", "alice", "node1")
         mock_swap.assert_called_once_with("node1", "bob", "alice", "bob", 
"alice", add=True)
-        mock_invoke.assert_called_once_with(
-            "bob",
-            "ssh {} alice@node1 sudo crm cluster init 
ssh_remote".format(constants.SSH_OPTION),
-        )
         mock_swap_2.assert_called_once()
         args, kwargs = mock_swap_2.call_args
         self.assertEqual(3, len(args))
@@ -802,45 +772,6 @@
             mock.call('node2', 'hacluster', 'hacluster', 'carol', 'bob', 
add=True)
             ])
 
-    @mock.patch('crmsh.userdir.getuser')
-    @mock.patch('crmsh.bootstrap.key_files')
-    @mock.patch('builtins.open')
-    @mock.patch('crmsh.bootstrap.append')
-    @mock.patch('os.path.join')
-    @mock.patch('os.path.exists')
-    def test_init_ssh_remote_no_sshkey(self, mock_exists, mock_join, 
mock_append, mock_open_file, mock_key_files, mock_getuser):
-        mock_getuser.return_value = "alice"
-        mock_key_files.return_value = {"private": "/home/alice/.ssh/id_rsa", 
"public": "/home/alice/.ssh/id_rsa.pub", "authorized": 
"/home/alice/.ssh/authorized_keys"}
-        mock_exists.side_effect = [False, True, False, False, False]
-        mock_join.side_effect = ["/home/alice/.ssh/id_rsa",
-                                 "/home/alice/.ssh/id_dsa",
-                                 "/home/alice/.ssh/id_ecdsa",
-                                 "/home/alice/.ssh/id_ed25519"]
-        mock_open_file.side_effect = [
-            mock.mock_open().return_value,
-            mock.mock_open(read_data="data1 data2").return_value,
-            mock.mock_open(read_data="data1111").return_value
-        ]
-
-        bootstrap.init_ssh_remote()
-
-        mock_getuser.assert_called_once_with()
-        mock_key_files.assert_called_once_with("alice")
-
-        mock_open_file.assert_has_calls([
-            mock.call("/home/alice/.ssh/authorized_keys", 'w'),
-            mock.call("/home/alice/.ssh/authorized_keys", "r+"),
-            mock.call("/home/alice/.ssh/id_rsa.pub")
-        ])
-        mock_exists.assert_has_calls([
-            mock.call("/home/alice/.ssh/authorized_keys"),
-            mock.call("/home/alice/.ssh/id_rsa"),
-            mock.call("/home/alice/.ssh/id_dsa"),
-            mock.call("/home/alice/.ssh/id_ecdsa"),
-            mock.call("/home/alice/.ssh/id_ed25519"),
-        ])
-        mock_append.assert_called_once_with("/home/alice/.ssh/id_rsa.pub", 
"/home/alice/.ssh/authorized_keys")
-
     @mock.patch('crmsh.sh.ClusterShell.get_rc_stdout_stderr_without_input')
     def test_get_node_canonical_hostname(self, mock_run):
         mock_run.return_value = (0, "Node1", None)
@@ -991,6 +922,7 @@
         mock_status.assert_not_called()
         mock_disable.assert_called_once_with("corosync-qdevice.service")
 
+    
@mock.patch('crmsh.bootstrap._select_user_pair_for_ssh_for_secondary_components')
     @mock.patch('crmsh.utils.HostUserConfig')
     @mock.patch('crmsh.user_of_host.UserOfHost.instance')
     @mock.patch('crmsh.utils.list_cluster_nodes')
@@ -1003,14 +935,15 @@
             mock_status, mock_check_ssh_passwd_need,
             mock_configure_ssh_key, mock_ssh_copy_id, mock_list_nodes, 
mock_user_of_host,
             mock_host_user_config_class,
+            mock_select_user_pair_for_ssh,
     ):
         mock_list_nodes.return_value = []
         bootstrap._context = mock.Mock(qdevice_inst=self.qdevice_with_ip, 
current_user="bob")
         mock_check_ssh_passwd_need.return_value = True
         mock_ssh_copy_id.return_value = 255
         mock_user_of_host.return_value = 
mock.MagicMock(crmsh.user_of_host.UserOfHost)
-        mock_user_of_host.return_value.user_pair_for_ssh.return_value = "bob", 
"bob"
         mock_user_of_host.return_value.use_ssh_agent.return_value = False
+        mock_select_user_pair_for_ssh.return_value = ("bob", "bob", 
'qnetd-node')
 
         with self.assertRaises(ValueError):
             bootstrap.init_qdevice()
@@ -1018,10 +951,11 @@
         mock_status.assert_has_calls([
             mock.call("Configure Qdevice/Qnetd:"),
         ])
-        mock_check_ssh_passwd_need.assert_called_once_with("bob", "bob", 
"10.10.10.123")
+        mock_check_ssh_passwd_need.assert_called_once_with("bob", "bob", 
"qnetd-node")
         mock_configure_ssh_key.assert_called_once_with('bob')
-        mock_ssh_copy_id.assert_called_once_with('bob', 'bob', '10.10.10.123')
+        mock_ssh_copy_id.assert_called_once_with('bob', 'bob', 'qnetd-node')
 
+    
@mock.patch('crmsh.bootstrap._select_user_pair_for_ssh_for_secondary_components')
     @mock.patch('crmsh.utils.HostUserConfig')
     @mock.patch('crmsh.user_of_host.UserOfHost.instance')
     @mock.patch('crmsh.utils.list_cluster_nodes')
@@ -1035,27 +969,29 @@
             mock_status, mock_ssh, mock_configure_ssh_key,
             mock_qdevice_configured, mock_confirm, mock_list_nodes, 
mock_user_of_host,
             mock_host_user_config_class,
+            mock_select_user_pair_for_ssh,
     ):
         mock_list_nodes.return_value = []
         bootstrap._context = mock.Mock(qdevice_inst=self.qdevice_with_ip, 
current_user="bob")
         mock_ssh.return_value = False
         mock_user_of_host.return_value = 
mock.MagicMock(crmsh.user_of_host.UserOfHost)
-        mock_user_of_host.return_value.user_pair_for_ssh.return_value = "bob", 
"bob"
         mock_user_of_host.return_value.use_ssh_agent.return_value = False
         mock_qdevice_configured.return_value = True
         mock_confirm.return_value = False
         self.qdevice_with_ip.start_qdevice_service = mock.Mock()
+        mock_select_user_pair_for_ssh.return_value = ("bob", "bob", 
'qnetd-node')
 
         bootstrap.init_qdevice()
 
         mock_status.assert_called_once_with("Configure Qdevice/Qnetd:")
-        mock_ssh.assert_called_once_with("bob", "bob", "10.10.10.123")
+        mock_ssh.assert_called_once_with("bob", "bob", "qnetd-node")
         mock_configure_ssh_key.assert_not_called()
         
mock_host_user_config_class.return_value.save_remote.assert_called_once_with(mock_list_nodes.return_value)
         mock_qdevice_configured.assert_called_once_with()
         mock_confirm.assert_called_once_with("Qdevice is already configured - 
overwrite?")
         self.qdevice_with_ip.start_qdevice_service.assert_called_once_with()
 
+    
@mock.patch('crmsh.bootstrap._select_user_pair_for_ssh_for_secondary_components')
     @mock.patch('crmsh.utils.HostUserConfig')
     @mock.patch('crmsh.user_of_host.UserOfHost.instance')
     @mock.patch('crmsh.bootstrap.adjust_priority_fencing_delay')
@@ -1068,26 +1004,26 @@
     @mock.patch('logging.Logger.info')
     def test_init_qdevice(self, mock_info, mock_ssh, mock_configure_ssh_key, 
mock_qdevice_configured,
                           mock_this_node, mock_list_nodes, 
mock_adjust_priority, mock_adjust_fence_delay,
-                          mock_user_of_host, mock_host_user_config_class):
+                          mock_user_of_host, mock_host_user_config_class, 
mock_select_user_pair_for_ssh):
         bootstrap._context = mock.Mock(qdevice_inst=self.qdevice_with_ip, 
current_user="bob")
         mock_this_node.return_value = "192.0.2.100"
         mock_list_nodes.return_value = []
         mock_ssh.return_value = False
         mock_user_of_host.return_value = 
mock.MagicMock(crmsh.user_of_host.UserOfHost)
-        mock_user_of_host.return_value.user_pair_for_ssh.return_value = "bob", 
"bob"
         mock_user_of_host.return_value.use_ssh_agent.return_value = False
         mock_qdevice_configured.return_value = False
         self.qdevice_with_ip.set_cluster_name = mock.Mock()
         self.qdevice_with_ip.valid_qnetd = mock.Mock()
         self.qdevice_with_ip.config_and_start_qdevice = mock.Mock()
+        mock_select_user_pair_for_ssh.return_value = ("bob", "bob", 
"qnetd-node")
 
         bootstrap.init_qdevice()
 
         mock_info.assert_called_once_with("Configure Qdevice/Qnetd:")
-        mock_ssh.assert_called_once_with("bob", "bob", "10.10.10.123")
+        mock_ssh.assert_called_once_with("bob", "bob", "qnetd-node")
         mock_host_user_config_class.return_value.add.assert_has_calls([
             mock.call('bob', '192.0.2.100'),
-            mock.call('bob', '10.10.10.123'),
+            mock.call('bob', 'qnetd-node'),
         ])
         
mock_host_user_config_class.return_value.save_remote.assert_called_once_with(mock_list_nodes.return_value)
         mock_qdevice_configured.assert_called_once_with()
@@ -1163,8 +1099,7 @@
         bootstrap.configure_qdevice_interactive()
         mock_confirm.assert_called_once_with("Do you want to configure 
QDevice?")
         mock_prompt.assert_has_calls([
-            mock.call("HOST or IP of the QNetd server to be used",
-                valid_func=qdevice.QDevice.check_qnetd_addr),
+            mock.call("HOST or IP of the QNetd server to be used"),
             mock.call("TCP PORT of QNetd server", default=5403,
                 valid_func=qdevice.QDevice.check_qdevice_port),
             mock.call("QNetd decision ALGORITHM (ffsplit/lms)", 
default="ffsplit",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.6.0+20240313.8278d949/test/unittests/test_utils.py 
new/crmsh-4.6.0+20240330.3473a5ba/test/unittests/test_utils.py
--- old/crmsh-4.6.0+20240313.8278d949/test/unittests/test_utils.py      
2024-03-13 14:56:37.000000000 +0100
+++ new/crmsh-4.6.0+20240330.3473a5ba/test/unittests/test_utils.py      
2024-03-30 12:48:39.000000000 +0100
@@ -42,36 +42,6 @@
     mock_run.assert_called_once_with("rpm -q --quiet crmsh")
 
 
[email protected]('crmsh.utils.detect_file')
-def test_check_file_content_included_target_not_exist(mock_detect):
-    mock_detect.side_effect = [True, False]
-    res = utils.check_file_content_included("file1", "file2")
-    assert res is False
-    mock_detect.assert_has_calls([
-        mock.call("file1", remote=None),
-        mock.call("file2", remote=None)
-        ])
-
-
[email protected]('crmsh.sh.ClusterShell.get_stdout_or_raise_error')
[email protected]('crmsh.utils.detect_file')
-def test_check_file_content_included(mock_detect, mock_run):
-    mock_detect.side_effect = [True, True]
-    mock_run.side_effect = ["data data", "data"]
-
-    res = utils.check_file_content_included("file1", "file2")
-    assert res is True
-
-    mock_detect.assert_has_calls([
-        mock.call("file1", remote=None),
-        mock.call("file2", remote=None)
-        ])
-    mock_run.assert_has_calls([
-        mock.call("cat file2", host=None),
-        mock.call("cat file1", host=None)
-        ])
-
-
 @mock.patch('re.search')
 @mock.patch('crmsh.sh.ShellUtils.get_stdout')
 def test_get_nodeid_from_name_run_None1(mock_get_stdout, mock_re_search):

Reply via email to