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):
