Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package crmsh for openSUSE:Factory checked in at 2026-03-13 21:16:57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Fri Mar 13 21:16:57 2026 rev:399 rq:1338590 version:5.0.0+20260313.6235a908 Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2026-03-11 20:57:30.947358707 +0100 +++ /work/SRC/openSUSE:Factory/.crmsh.new.8177/crmsh.changes 2026-03-13 21:21:11.264191042 +0100 @@ -1,0 +2,19 @@ +Fri Mar 13 04:23:58 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260313.6235a908: + * Dev: sh: Use sh_helper.py for su commands (bsc#1254757) + +------------------------------------------------------------------- +Thu Mar 12 09:00:03 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260312.ad57a9db: + * Dev: testcases: remove traceback + * Fix: log: Disable color when not on a TTY (bsc#1259178) + +------------------------------------------------------------------- +Wed Mar 11 07:47:27 UTC 2026 - [email protected] + +- Update to version 5.0.0+20260311.dfa9856b: + * Dev: bootstrap: Skip inactive cluster node when calling restart_cluster function + +------------------------------------------------------------------- Old: ---- crmsh-5.0.0+20260309.5a3c6578.tar.bz2 New: ---- crmsh-5.0.0+20260313.6235a908.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.Jl7rz3/_old 2026-03-13 21:21:11.984220744 +0100 +++ /var/tmp/diff_new_pack.Jl7rz3/_new 2026-03-13 21:21:11.988220909 +0100 @@ -41,7 +41,7 @@ Summary: High Availability cluster command-line interface License: GPL-2.0-or-later Group: %{pkg_group} -Version: 5.0.0+20260309.5a3c6578 +Version: 5.0.0+20260313.6235a908 Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.Jl7rz3/_old 2026-03-13 21:21:12.044223219 +0100 +++ /var/tmp/diff_new_pack.Jl7rz3/_new 2026-03-13 21:21:12.052223549 +0100 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">5a3c65789ebe0308f5b38e20ec0c50ff80fafc04</param> + <param name="changesrevision">6235a908e9f921dcff4e366ac780fd4c1d86e9d3</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-5.0.0+20260309.5a3c6578.tar.bz2 -> crmsh-5.0.0+20260313.6235a908.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260309.5a3c6578/crmsh/bootstrap.py new/crmsh-5.0.0+20260313.6235a908/crmsh/bootstrap.py --- old/crmsh-5.0.0+20260309.5a3c6578/crmsh/bootstrap.py 2026-03-09 15:27:39.000000000 +0100 +++ new/crmsh-5.0.0+20260313.6235a908/crmsh/bootstrap.py 2026-03-13 04:18:36.000000000 +0100 @@ -2876,8 +2876,17 @@ def restart_cluster(): + service_manager = ServiceManager() + node_list = utils.list_cluster_nodes() + for node in node_list[:]: + if not service_manager.service_is_active(constants.PCMK_SERVICE, remote_addr=node): + logger.warning("Cluster is inactive on %s, skip restarting cluster service on it", node) + node_list.remove(node) + if not node_list: + return + logger.info("Restarting cluster service") - utils.cluster_run_cmd("crm cluster restart") + utils.cluster_run_cmd("crm cluster restart", node_list) wait_for_cluster() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260309.5a3c6578/crmsh/log.py new/crmsh-5.0.0+20260313.6235a908/crmsh/log.py --- old/crmsh-5.0.0+20260309.5a3c6578/crmsh/log.py 2026-03-09 15:27:39.000000000 +0100 +++ new/crmsh-5.0.0+20260313.6235a908/crmsh/log.py 2026-03-13 04:18:36.000000000 +0100 @@ -284,7 +284,7 @@ NO_COLOR_FORMATTERS = { "console_report": { "()": LeveledFormatter, - "base_formatter_factory": logging.Formatter, + "base_formatter_factory": NoBacktraceFormatter, "default_fmt": "{}: %(levelname)s: %(message)s".format(socket.gethostname()), "level_fmt": { DEBUG2: "{}: %(levelname)s: %(funcName)s: %(message)s".format(socket.gethostname()), @@ -292,7 +292,7 @@ }, "console": { "()": LeveledFormatter, - "base_formatter_factory": logging.Formatter, + "base_formatter_factory": NoBacktraceFormatter, "default_fmt": "%(levelname)s: %(message)s", "level_fmt": { DEBUG2: "%(levelname)s: %(funcName)s %(message)s", @@ -568,10 +568,11 @@ if os.environ.get('CRMSH_REGRESSION_TEST'): logging.setLoggerClass(NumberedLogger) LOGGING_CFG['formatters'] = NO_COLOR_FORMATTERS - logging.config.dictConfig(LOGGING_CFG) else: logging.setLoggerClass(NumberedLoggerInterface) - logging.config.dictConfig(LOGGING_CFG) + if not all(os.isatty(fd) for fd in range(3)): + LOGGING_CFG['formatters'] = NO_COLOR_FORMATTERS + logging.config.dictConfig(LOGGING_CFG) def setup_logger(name): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260309.5a3c6578/crmsh/prun/prun.py new/crmsh-5.0.0+20260313.6235a908/crmsh/prun/prun.py --- old/crmsh-5.0.0+20260309.5a3c6578/crmsh/prun/prun.py 2026-03-09 15:27:39.000000000 +0100 +++ new/crmsh-5.0.0+20260313.6235a908/crmsh/prun/prun.py 2026-03-13 04:18:36.000000000 +0100 @@ -1,7 +1,9 @@ # prun.py - run command or copy files on multiple hosts concurrently import os import random +import shlex import socket +import sys import tempfile import typing @@ -13,6 +15,8 @@ _DEFAULT_CONCURRENCY = 32 +_SH_HELPER = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'sh_helper.py') + _SUDO_SFTP_SERVER_OPTION = '-s \'sudo --preserve-env=SSH_AUTH_SOCK PATH=/usr/lib/ssh:/usr/lib/openssh:/usr/libexec/ssh:/usr/libexec/openssh /bin/sh -c "exec sftp-server"\'' @@ -125,7 +129,9 @@ if local_sudoer == crmsh.userdir.getuser(): args = ['/bin/sh', '-c', shell] elif os.geteuid() == 0: - args = ['su', local_sudoer, '--login', '-c', shell, '-w', 'SSH_AUTH_SOCK'] + helper_args = [sys.executable, _SH_HELPER, '--preserve-env', 'SSH_AUTH_SOCK', shell] + shell_cmd = ' '.join(shlex.quote(a) for a in helper_args) + args = ['su', '-s', '/bin/sh', '-w', 'SSH_AUTH_SOCK', '-c', shell_cmd, local_sudoer] else: raise AssertionError('trying to run su as a non-root user') return Task( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260309.5a3c6578/crmsh/sh.py new/crmsh-5.0.0+20260313.6235a908/crmsh/sh.py --- old/crmsh-5.0.0+20260309.5a3c6578/crmsh/sh.py 2026-03-09 15:27:39.000000000 +0100 +++ new/crmsh-5.0.0+20260313.6235a908/crmsh/sh.py 2026-03-13 04:18:36.000000000 +0100 @@ -22,8 +22,10 @@ import os import pwd import re +import shlex import socket import subprocess +import sys import typing from io import StringIO from functools import cache @@ -36,6 +38,8 @@ logger = logging.getLogger(__name__) +_SH_HELPER = os.path.join(os.path.dirname(__file__), 'sh_helper.py') + class Error(ValueError): def __init__(self, msg, cmd): @@ -140,12 +144,19 @@ if user is None or self.get_effective_user_name() == user: args = ['/bin/sh', '-c', cmd] elif 0 == self.geteuid(): - args = ['su', user, '--login', '-s', '/bin/sh', '-c', cmd] + helper_args = [sys.executable, _SH_HELPER] + if self.preserve_env: + helper_args.extend(['--preserve-env', ','.join(self.preserve_env)]) + helper_args.append(cmd) + shell_cmd = ' '.join(shlex.quote(a) for a in helper_args) + + args = ['su', '-s', '/bin/sh'] if tty: args.append('--pty') if self.preserve_env: args.append('-w') args.append(','.join(self.preserve_env)) + args.extend(['-c', shell_cmd, user]) else: raise AuthorizationError( cmd, None, user, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260309.5a3c6578/crmsh/sh_helper.py new/crmsh-5.0.0+20260313.6235a908/crmsh/sh_helper.py --- old/crmsh-5.0.0+20260309.5a3c6578/crmsh/sh_helper.py 1970-01-01 01:00:00.000000000 +0100 +++ new/crmsh-5.0.0+20260313.6235a908/crmsh/sh_helper.py 2026-03-13 04:18:36.000000000 +0100 @@ -0,0 +1,63 @@ +import argparse +import os +import pwd +import sys + + +def main(): + argument_parser = argparse.ArgumentParser(description="Helper script to run command in a clean environment") + argument_parser.add_argument('--preserve-env', help="Environment variables to preserve, separated by comma") + argument_parser.add_argument('command', help="Command to execute") + args = argument_parser.parse_args() + + # Get user info for USER, LOGNAME, and HOME + pw = pwd.getpwuid(os.getuid()) + user_name = pw.pw_name + user_home = pw.pw_dir + + # Prepare a clean environment + new_env = {} + + # 1. Keep TERM if it exists + if 'TERM' in os.environ: + new_env['TERM'] = os.environ['TERM'] + + # 2. Keep SHELL if it was initialized by su (or previous environment) + if 'SHELL' in os.environ: + new_env['SHELL'] = os.environ['SHELL'] + + # 3. Setup USER, LOGNAME and HOME + new_env['USER'] = user_name + new_env['LOGNAME'] = user_name + new_env['HOME'] = user_home + + # Handle preserve_env + if args.preserve_env: + for var in args.preserve_env.split(','): + var = var.strip() + val = os.environ.get(var) + if val is not None: + new_env[var] = val + + # Default PATH if not preserved + if 'PATH' not in new_env: + # A sensible default path + new_env['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' + + # Change to user's home directory + try: + os.chdir(new_env['HOME']) + except Exception as e: + print(f"Warning: Could not change directory to {new_env['HOME']}: {e}", file=sys.stderr) + sys.exit(1) + + # Replace the current process with /bin/bash -c <command> + try: + os.execve('/bin/bash', ['/bin/bash', '-c', args.command], new_env) + except Exception as e: + print(f"Failed to execute command: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260309.5a3c6578/test/features/bootstrap_bugs.feature new/crmsh-5.0.0+20260313.6235a908/test/features/bootstrap_bugs.feature --- old/crmsh-5.0.0+20260309.5a3c6578/test/features/bootstrap_bugs.feature 2026-03-09 15:27:39.000000000 +0100 +++ new/crmsh-5.0.0+20260313.6235a908/test/features/bootstrap_bugs.feature 2026-03-13 04:18:36.000000000 +0100 @@ -295,3 +295,15 @@ Then Cluster service is "stopped" on "hanode1" And Cluster service is "stopped" on "hanode2" And Cluster service is "stopped" on "hanode3" + + # skip non-root as non-root user cannot write to `/etc/profile` + @skip_non_root + @clean + Scenario: When '/etc/profile' prints garbages (bsc#1254757) + Given Cluster service is "stopped" on "hanode1" + And Cluster service is "stopped" on "hanode2" + When Run "echo 'echo Boom!' > /etc/profile" on "hanode1,hanode2" + When Run "crm cluster init -y" on "hanode1" + When Run "crm cluster join -c hanode1 -y" on "hanode2" + Then Cluster service is "started" on "hanode1" + Then Cluster service is "started" on "hanode2" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260309.5a3c6578/test/testcases/edit.exp new/crmsh-5.0.0+20260313.6235a908/test/testcases/edit.exp --- old/crmsh-5.0.0+20260309.5a3c6578/test/testcases/edit.exp 2026-03-09 15:27:39.000000000 +0100 +++ new/crmsh-5.0.0+20260313.6235a908/test/testcases/edit.exp 2026-03-13 04:18:36.000000000 +0100 @@ -71,24 +71,8 @@ ERROR: 1: syntax in group: child p1 listed more than once in group g1 parsing 'group g1 p1 p2 d3 p1' .INP: modgroup g1 remove c1 ERROR: 39: configure.modgroup: c1 is not member of g1 -Traceback (most recent call last): - rv = self.execute_command() is not False - ~~~~~~~~~~~~~~~~~~~~^^ - rv = self.command_info.function(*arglist) - context.fatal_error("%s is not member of %s" % (prim_id, group_id)) - ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - raise ValueError(msg) -ValueError: c1 is not member of g1 .INP: modgroup g1 remove nosuch ERROR: 40: configure.modgroup: nosuch is not member of g1 -Traceback (most recent call last): - rv = self.execute_command() is not False - ~~~~~~~~~~~~~~~~~~~~^^ - rv = self.command_info.function(*arglist) - context.fatal_error("%s is not member of %s" % (prim_id, group_id)) - ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - raise ValueError(msg) -ValueError: nosuch is not member of g1 .INP: modgroup g1 add c1 ERROR: 41: a group may contain only primitives; c1 is clone .INP: modgroup g1 add nosuch diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260309.5a3c6578/test/testcases/shadow.exp new/crmsh-5.0.0+20260313.6235a908/test/testcases/shadow.exp --- old/crmsh-5.0.0+20260309.5a3c6578/test/testcases/shadow.exp 2026-03-09 15:27:39.000000000 +0100 +++ new/crmsh-5.0.0+20260313.6235a908/test/testcases/shadow.exp 2026-03-13 04:18:36.000000000 +0100 @@ -12,14 +12,6 @@ INFO: 5: cib.commit: committed 'regtest' shadow CIB to the cluster .INP: delete regtest ERROR: 6: cib.delete: regtest shadow CIB is in use -Traceback (most recent call last): - rv = self.execute_command() is not False - ~~~~~~~~~~~~~~~~~~~~^^ - rv = self.command_info.function(*arglist) - context.fatal_error("%s shadow CIB is in use" % name) - ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - raise ValueError(msg) -ValueError: regtest shadow CIB is in use .INP: use .INP: delete regtest .EXT >/dev/null </dev/null crm_shadow -b -D 'regtest' --force diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260309.5a3c6578/test/unittests/test_prun.py new/crmsh-5.0.0+20260313.6235a908/test/unittests/test_prun.py --- old/crmsh-5.0.0+20260309.5a3c6578/test/unittests/test_prun.py 2026-03-09 15:27:39.000000000 +0100 +++ new/crmsh-5.0.0+20260313.6235a908/test/unittests/test_prun.py 2026-03-13 04:18:36.000000000 +0100 @@ -1,4 +1,6 @@ import typing +import sys +import shlex import crmsh.constants import crmsh.prun.prun @@ -38,16 +40,26 @@ mock.call("host1"), mock.call("host2"), ]) + shell1 = 'ssh -A {} bob@host1 sudo -H /bin/bash'.format(crmsh.constants.SSH_OPTION) + helper_args1 = [sys.executable, crmsh.prun.prun._SH_HELPER, '--preserve-env', 'SSH_AUTH_SOCK', shell1] + shell_cmd1 = ' '.join(shlex.quote(a) for a in helper_args1) + args1 = ['su', '-s', '/bin/sh', '-w', 'SSH_AUTH_SOCK', '-c', shell_cmd1, 'alice'] + + shell2 = 'ssh -A {} bob@host2 sudo -H /bin/bash'.format(crmsh.constants.SSH_OPTION) + helper_args2 = [sys.executable, crmsh.prun.prun._SH_HELPER, '--preserve-env', 'SSH_AUTH_SOCK', shell2] + shell_cmd2 = ' '.join(shlex.quote(a) for a in helper_args2) + args2 = ['su', '-s', '/bin/sh', '-w', 'SSH_AUTH_SOCK', '-c', shell_cmd2, 'alice'] + mock_runner_add_task.assert_has_calls([ mock.call(TaskArgumentsEq( - ['su', 'alice', '--login', '-c', 'ssh -A {} bob@host1 sudo -H /bin/bash'.format(crmsh.constants.SSH_OPTION), '-w', 'SSH_AUTH_SOCK'], + args1, b'foo', stdout=crmsh.prun.runner.Task.Capture, stderr=crmsh.prun.runner.Task.Capture, context={"host": 'host1', "ssh_user": 'bob'}, )), mock.call(TaskArgumentsEq( - ['su', 'alice', '--login', '-c', 'ssh -A {} bob@host2 sudo -H /bin/bash'.format(crmsh.constants.SSH_OPTION), '-w', 'SSH_AUTH_SOCK'], + args2, b'bar', stdout=crmsh.prun.runner.Task.Capture, stderr=crmsh.prun.runner.Task.Capture, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.0.0+20260309.5a3c6578/test/unittests/test_sh.py new/crmsh-5.0.0+20260313.6235a908/test/unittests/test_sh.py --- old/crmsh-5.0.0+20260309.5a3c6578/test/unittests/test_sh.py 2026-03-09 15:27:39.000000000 +0100 +++ new/crmsh-5.0.0+20260313.6235a908/test/unittests/test_sh.py 2026-03-13 04:18:36.000000000 +0100 @@ -1,3 +1,6 @@ +import os +import sys +import shlex import subprocess import unittest import pickle @@ -23,8 +26,11 @@ 'alice', 'foo', input=b'bar', ) + sh_helper = os.path.join(os.path.dirname(crmsh.sh.__file__), 'sh_helper.py') + helper_args = [sys.executable, sh_helper, 'foo'] + shell_cmd = ' '.join(shlex.quote(a) for a in helper_args) mock_run.assert_called_once_with( - ['su', 'alice', '--login', '-s', '/bin/sh', '-c', 'foo'], + ['su', '-s', '/bin/sh', '-c', shell_cmd, 'alice'], input=b'bar', env=mock_environ, )
