From ce790809230a617da873f04bec2e153a813e01ca Mon Sep 17 00:00:00 2001
From: Victor Zhestkov <vzhestkov@suse.com>
Date: Tue, 12 Apr 2022 10:06:09 +0300
Subject: [PATCH] Prevent affection of SSH.opts with LazyLoader
 (bsc#1197637)

* Prevent affection SSH.opts with LazyLoader

* Restore parsed targets

* Fix test_ssh unit tests
---
 salt/client/ssh/__init__.py   | 19 +++++++++++--------
 tests/unit/client/test_ssh.py | 16 ++++++++--------
 2 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py
index b9c69f17e3..26248588e1 100644
--- a/salt/client/ssh/__init__.py
+++ b/salt/client/ssh/__init__.py
@@ -225,15 +225,16 @@ class SSH:
     ROSTER_UPDATE_FLAG = "#__needs_update"
 
     def __init__(self, opts, context=None):
+        self.opts = copy.deepcopy(opts)
+        self.sopts = copy.deepcopy(self.opts)
         self.__parsed_rosters = {SSH.ROSTER_UPDATE_FLAG: True}
-        pull_sock = os.path.join(opts["sock_dir"], "master_event_pull.ipc")
+        pull_sock = os.path.join(self.opts["sock_dir"], "master_event_pull.ipc")
         if os.path.exists(pull_sock) and zmq:
             self.event = salt.utils.event.get_event(
-                "master", opts["sock_dir"], opts["transport"], opts=opts, listen=False
+                "master", self.opts["sock_dir"], self.opts["transport"], opts=self.opts, listen=False
             )
         else:
             self.event = None
-        self.opts = opts
         if self.opts["regen_thin"]:
             self.opts["ssh_wipe"] = True
         if not salt.utils.path.which("ssh"):
@@ -241,7 +242,7 @@ class SSH:
                 code=-1,
                 msg="No ssh binary found in path -- ssh must be installed for salt-ssh to run. Exiting.",
             )
-        self.opts["_ssh_version"] = ssh_version()
+        self.sopts["_ssh_version"] = ssh_version()
         self.tgt_type = (
             self.opts["selected_target_option"]
             if self.opts["selected_target_option"]
@@ -340,6 +341,9 @@ class SSH:
             self.opts["cachedir"], "salt-ssh.session.lock"
         )
         self.ssh_session_grace_time = int(self.opts.get("ssh_session_grace_time", 1))
+        self.sopts["tgt"] = copy.deepcopy(self.opts["tgt"])
+        self.sopts["ssh_cli_tgt"] = copy.deepcopy(self.opts["ssh_cli_tgt"])
+        self.opts = self.sopts
 
     @property
     def parse_tgt(self):
@@ -596,7 +600,6 @@ class SSH:
         Spin up the needed threads or processes and execute the subsequent
         routines
         """
-        opts = copy.deepcopy(self.opts)
         que = multiprocessing.Queue()
         running = {}
         targets_queue = deque(self.targets.keys())
@@ -607,7 +610,7 @@ class SSH:
             if not self.targets:
                 log.error("No matching targets found in roster.")
                 break
-            if len(running) < opts.get("ssh_max_procs", 25) and not init:
+            if len(running) < self.opts.get("ssh_max_procs", 25) and not init:
                 if targets_queue:
                     host = targets_queue.popleft()
                 else:
@@ -668,7 +671,7 @@ class SSH:
                     continue
                 args = (
                     que,
-                    opts,
+                    self.opts,
                     host,
                     self.targets[host],
                     mine,
@@ -766,7 +769,7 @@ class SSH:
             if len(rets) >= len(self.targets):
                 break
             # Sleep when limit or all threads started
-            if len(running) >= opts.get("ssh_max_procs", 25) or len(
+            if len(running) >= self.opts.get("ssh_max_procs", 25) or len(
                 self.targets
             ) >= len(running):
                 time.sleep(0.1)
diff --git a/tests/unit/client/test_ssh.py b/tests/unit/client/test_ssh.py
index 75af4adc23..1a7fcdcd45 100644
--- a/tests/unit/client/test_ssh.py
+++ b/tests/unit/client/test_ssh.py
@@ -95,7 +95,7 @@ class SSHReturnEventTests(ShellCase):
             assert "localhost" in ret
             assert "fun" in ret["localhost"]
             client.run()
-        display_output.assert_called_once_with(expected, "nested", opts)
+        display_output.assert_called_once_with(expected, "nested", client.opts)
         self.assertIs(ret, handle_ssh_ret[0])
         assert len(client.event.fire_event.call_args_list) == 2
         assert "fun" in client.event.fire_event.call_args_list[0][0][0]
@@ -540,7 +540,7 @@ class SSHTests(ShellCase):
             MagicMock(return_value=salt.utils.yaml.safe_load(self.roster)),
         ):
             client._expand_target()
-        assert opts["tgt"] == host
+        assert client.opts["tgt"] == host
 
     def test_expand_target_dns(self):
         """
@@ -563,7 +563,7 @@ class SSHTests(ShellCase):
             MagicMock(return_value=salt.utils.yaml.safe_load(self.roster)),
         ):
             client._expand_target()
-        assert opts["tgt"] == host
+        assert client.opts["tgt"] == host
 
     def test_expand_target_no_user(self):
         """
@@ -603,7 +603,7 @@ class SSHTests(ShellCase):
             client = ssh.SSH(opts)
         assert opts["tgt"] == user + host
         client._update_targets()
-        assert opts["tgt"] == host
+        assert client.opts["tgt"] == host
         assert client.targets[host]["user"] == user.split("@")[0]
 
     def test_update_targets_dns(self):
@@ -621,7 +621,7 @@ class SSHTests(ShellCase):
             client = ssh.SSH(opts)
         assert opts["tgt"] == user + host
         client._update_targets()
-        assert opts["tgt"] == host
+        assert client.opts["tgt"] == host
         assert client.targets[host]["user"] == user.split("@")[0]
 
     def test_update_targets_no_user(self):
@@ -662,7 +662,7 @@ class SSHTests(ShellCase):
         ):
             client._expand_target()
         client._update_targets()
-        assert opts["tgt"] == host
+        assert client.opts["tgt"] == host
         assert client.targets[host]["user"] == user.split("@")[0]
 
     def test_parse_tgt(self):
@@ -682,7 +682,7 @@ class SSHTests(ShellCase):
             client = ssh.SSH(opts)
             assert client.parse_tgt["hostname"] == host
             assert client.parse_tgt["user"] == user.split("@")[0]
-            assert self.opts.get("ssh_cli_tgt") == user + host
+            assert client.opts.get("ssh_cli_tgt") == user + host
 
     def test_parse_tgt_no_user(self):
         """
@@ -701,4 +701,4 @@ class SSHTests(ShellCase):
             client = ssh.SSH(opts)
             assert client.parse_tgt["hostname"] == host
             assert client.parse_tgt["user"] == opts["ssh_user"]
-            assert self.opts.get("ssh_cli_tgt") == host
+            assert client.opts.get("ssh_cli_tgt") == host
-- 
2.35.1


