diff --git a/_lastrevision b/_lastrevision
index a78ea85..dab765c 100644
--- a/_lastrevision
+++ b/_lastrevision
@@ -1 +1 @@
-a9b3f5bf8fd572965c589d95a7f99d36f7f37b62
\ No newline at end of file
+4e81748d5e88d323e700a458ca0e9680acc81927
\ No newline at end of file
diff --git a/backport-batch-async-fixes-and-improvements-701.patch b/backport-batch-async-fixes-and-improvements-701.patch
new file mode 100644
index 0000000..aabd309
--- /dev/null
+++ b/backport-batch-async-fixes-and-improvements-701.patch
@@ -0,0 +1,336 @@
+From 4fe7231fa99de8edc848367386f1a6a5192a0f58 Mon Sep 17 00:00:00 2001
+From: Victor Zhestkov
+Date: Fri, 21 Feb 2025 11:15:42 +0100
+Subject: [PATCH] Backport batch async fixes and improvements (#701)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+* Backport batch async fixes and improvements
+
+Co-authored-by: Pablo Suárez Hernández
+
+* Align batch_async tests
+
+---------
+
+Co-authored-by: Pablo Suárez Hernández
+---
+ salt/cli/batch_async.py | 60 ++++++++++++++++-----
+ tests/pytests/unit/cli/test_batch_async.py | 63 ++++++++--------------
+ 2 files changed, 69 insertions(+), 54 deletions(-)
+
+diff --git a/salt/cli/batch_async.py b/salt/cli/batch_async.py
+index 5d49993faa..92215d0e04 100644
+--- a/salt/cli/batch_async.py
++++ b/salt/cli/batch_async.py
+@@ -35,7 +35,7 @@ def batch_async_required(opts, minions, extra):
+ Check opts to identify if batch async is required for the operation.
+ """
+ if not isinstance(minions, list):
+- False
++ return False
+ batch_async_opts = opts.get("batch_async", {})
+ batch_async_threshold = (
+ batch_async_opts.get("threshold", 1)
+@@ -179,6 +179,7 @@ class SharedEventsChannel:
+ self._used_by.discard(subscriber_id)
+
+ def destroy_unused(self):
++ log.trace("SharedEventsChannel.destroy_unused called")
+ if self._used_by:
+ return False
+ self.master_event.remove_event_handler(self.__handle_event)
+@@ -267,6 +268,7 @@ class BatchAsync:
+ self.ended = False
+ self.event = self.events_channel.master_event
+ self.scheduled = False
++ self._start_batch_on_timeout = None
+
+ def __set_event_handler(self):
+ self.events_channel.subscribe(
+@@ -278,6 +280,8 @@ class BatchAsync:
+
+ @salt.ext.tornado.gen.coroutine
+ def __event_handler(self, tag, data, op):
++ # IMPORTANT: This function must run fast and not wait for any other task,
++ # otherwise it would cause events to be stuck.
+ if not self.event:
+ return
+ try:
+@@ -285,7 +289,9 @@ class BatchAsync:
+ if op == "ping_return":
+ self.minions.add(minion)
+ if self.targeted_minions == self.minions:
+- yield self.start_batch()
++ # call start_batch and do not wait for timeout as we received
++ # the responses from all the targets
++ self.io_loop.add_callback(self.start_batch)
+ elif op == "find_job_return":
+ if data.get("return", None):
+ self.find_job_returned.add(minion)
+@@ -293,7 +299,8 @@ class BatchAsync:
+ if minion in self.active:
+ self.active.remove(minion)
+ self.done_minions.add(minion)
+- yield self.schedule_next()
++ if not self.active:
++ self.io_loop.add_callback(self.schedule_next)
+ except Exception as ex: # pylint: disable=W0703
+ log.error(
+ "Exception occured while processing event: %s: %s",
+@@ -333,7 +340,7 @@ class BatchAsync:
+ )
+
+ if timedout_minions:
+- yield self.schedule_next()
++ self.io_loop.add_callback(self.schedule_next)
+
+ if self.event and running:
+ self.find_job_returned = self.find_job_returned.difference(running)
+@@ -344,6 +351,9 @@ class BatchAsync:
+ """
+ Find if the job was finished on the minions
+ """
++ log.trace(
++ "[%s] BatchAsync.find_job called for minions: %s", self.batch_jid, minions
++ )
+ if not self.event:
+ return
+ not_done = minions.difference(self.done_minions).difference(
+@@ -386,6 +396,7 @@ class BatchAsync:
+ if not self.event:
+ return
+ self.__set_event_handler()
++ # call test.ping for all the targets in async way
+ ping_return = yield self.events_channel.local_client.run_job_async(
+ self.opts["tgt"],
+ "test.ping",
+@@ -398,19 +409,24 @@ class BatchAsync:
+ listen=False,
+ **self.eauth,
+ )
++ # ping_return contains actual targeted minions and no actual responses
++ # from the minions as it's async and intended to populate targeted_minions set
+ self.targeted_minions = set(ping_return["minions"])
+- # start batching even if not all minions respond to ping
+- yield salt.ext.tornado.gen.sleep(
+- self.batch_presence_ping_timeout or self.opts["gather_job_timeout"]
++ # schedule start_batch to perform even if not all the minions responded
++ # self.__event_handler can push start_batch in case if all targets responded
++ self._start_batch_on_timeout = self.io_loop.call_later(
++ self.batch_presence_ping_timeout or self.opts["gather_job_timeout"],
++ self.start_batch,
+ )
+- if self.event:
+- yield self.start_batch()
+
+ @salt.ext.tornado.gen.coroutine
+ def start_batch(self):
+ """
+ Fire `salt/batch/*/start` and continue batch with `run_next`
+ """
++ if self._start_batch_on_timeout is not None:
++ self.io_loop.remove_timeout(self._start_batch_on_timeout)
++ self._start_batch_on_timeout = None
+ if self.initialized:
+ return
+ self.batch_size = get_bnum(self.opts, self.minions, True)
+@@ -431,6 +447,7 @@ class BatchAsync:
+ """
+ End the batch and call safe closing
+ """
++ log.trace("[%s] BatchAsync.end_batch called", self.batch_jid)
+ left = self.minions.symmetric_difference(
+ self.done_minions.union(self.timedout_minions)
+ )
+@@ -452,10 +469,11 @@ class BatchAsync:
+
+ # release to the IOLoop to allow the event to be published
+ # before closing batch async execution
+- yield salt.ext.tornado.gen.sleep(1)
++ yield salt.ext.tornado.gen.sleep(0.03)
+ self.close_safe()
+
+ def close_safe(self):
++ log.trace("[%s] BatchAsync.close_safe called", self.batch_jid)
+ if self.events_channel is not None:
+ self.events_channel.unsubscribe(None, None, id(self))
+ self.events_channel.unuse(id(self))
+@@ -465,11 +483,22 @@ class BatchAsync:
+
+ @salt.ext.tornado.gen.coroutine
+ def schedule_next(self):
++ log.trace("[%s] BatchAsync.schedule_next called", self.batch_jid)
+ if self.scheduled:
++ log.trace(
++ "[%s] BatchAsync.schedule_next -> Batch already scheduled, nothing to do.",
++ self.batch_jid,
++ )
+ return
+ self.scheduled = True
+- # call later so that we maybe gather more returns
+- yield salt.ext.tornado.gen.sleep(self.batch_delay)
++ if self._get_next():
++ # call later so that we maybe gather more returns
++ log.trace(
++ "[%s] BatchAsync.schedule_next delaying batch %s second(s).",
++ self.batch_jid,
++ self.batch_delay,
++ )
++ yield salt.ext.tornado.gen.sleep(self.batch_delay)
+ if self.event:
+ yield self.run_next()
+
+@@ -480,6 +509,11 @@ class BatchAsync:
+ """
+ self.scheduled = False
+ next_batch = self._get_next()
++ log.trace(
++ "[%s] BatchAsync.run_next called. Next Batch -> %s",
++ self.batch_jid,
++ next_batch,
++ )
+ if not next_batch:
+ yield self.end_batch()
+ return
+@@ -504,7 +538,7 @@ class BatchAsync:
+ yield salt.ext.tornado.gen.sleep(self.opts["timeout"])
+
+ # The batch can be done already at this point, which means no self.event
+- if self.event:
++ if self.event and self.active.intersection(next_batch):
+ yield self.find_job(set(next_batch))
+ except Exception as ex: # pylint: disable=W0703
+ log.error(
+diff --git a/tests/pytests/unit/cli/test_batch_async.py b/tests/pytests/unit/cli/test_batch_async.py
+index bc871aba54..be8de692e6 100644
+--- a/tests/pytests/unit/cli/test_batch_async.py
++++ b/tests/pytests/unit/cli/test_batch_async.py
+@@ -85,11 +85,17 @@ def test_batch_start_on_batch_presence_ping_timeout(batch):
+ future.set_result({})
+ with patch.object(batch, "events_channel", MagicMock()), patch(
+ "salt.ext.tornado.gen.sleep", return_value=future
+- ), patch.object(batch, "start_batch", return_value=future) as start_batch_mock:
++ ), patch.object(batch, "io_loop", MagicMock()), patch.object(
++ batch, "start_batch", return_value=future
++ ) as start_batch_mock:
+ batch.events_channel.local_client.run_job_async.return_value = future_ret
+ ret = batch.start()
+- # assert start_batch is called
+- start_batch_mock.assert_called_once()
++ # start_batch is scheduled to be called later
++ assert batch.io_loop.call_later.call_args[0] == (
++ batch.batch_presence_ping_timeout,
++ batch.start_batch,
++ )
++ assert batch._start_batch_on_timeout is not None
+ # assert test.ping called
+ assert batch.events_channel.local_client.run_job_async.call_args[0] == (
+ "*",
+@@ -109,16 +115,21 @@ def test_batch_start_on_gather_job_timeout(batch):
+ batch.batch_presence_ping_timeout = None
+ with patch.object(batch, "events_channel", MagicMock()), patch(
+ "salt.ext.tornado.gen.sleep", return_value=future
++ ), patch.object(batch, "io_loop", MagicMock()), patch.object(
++ batch, "start_batch", return_value=future
+ ), patch.object(
+ batch, "start_batch", return_value=future
+ ) as start_batch_mock, patch.object(
+ batch, "batch_presence_ping_timeout", None
+ ):
+ batch.events_channel.local_client.run_job_async.return_value = future_ret
+- # ret = batch_async.start(batch)
+ ret = batch.start()
+- # assert start_batch is called
+- start_batch_mock.assert_called_once()
++ # start_batch is scheduled to be called later
++ assert batch.io_loop.call_later.call_args[0] == (
++ batch.opts["gather_job_timeout"],
++ batch.start_batch,
++ )
++ assert batch._start_batch_on_timeout is not None
+
+
+ def test_batch_fire_start_event(batch):
+@@ -271,34 +282,10 @@ def test_batch__event_handler_ping_return(batch):
+ assert batch.done_minions == set()
+
+
+-def test_batch__event_handler_call_start_batch_when_all_pings_return(batch):
+- batch.targeted_minions = {"foo"}
+- future = salt.ext.tornado.gen.Future()
+- future.set_result({})
+- with patch.object(batch, "start_batch", return_value=future) as start_batch_mock:
+- batch.start()
+- batch._BatchAsync__event_handler(
+- "salt/job/1234/ret/foo", {"id": "foo"}, "ping_return"
+- )
+- start_batch_mock.assert_called_once()
+-
+-
+-def test_batch__event_handler_not_call_start_batch_when_not_all_pings_return(batch):
+- batch.targeted_minions = {"foo", "bar"}
+- future = salt.ext.tornado.gen.Future()
+- future.set_result({})
+- with patch.object(batch, "start_batch", return_value=future) as start_batch_mock:
+- batch.start()
+- batch._BatchAsync__event_handler(
+- "salt/job/1234/ret/foo", {"id": "foo"}, "ping_return"
+- )
+- start_batch_mock.assert_not_called()
+-
+-
+ def test_batch__event_handler_batch_run_return(batch):
+ future = salt.ext.tornado.gen.Future()
+ future.set_result({})
+- with patch.object(
++ with patch.object(batch, "io_loop", MagicMock()), patch.object(
+ batch, "schedule_next", return_value=future
+ ) as schedule_next_mock:
+ batch.start()
+@@ -308,7 +295,7 @@ def test_batch__event_handler_batch_run_return(batch):
+ )
+ assert batch.active == set()
+ assert batch.done_minions == {"foo"}
+- schedule_next_mock.assert_called_once()
++ batch.io_loop.add_callback.call_args[0] == (batch.schedule_next)
+
+
+ def test_batch__event_handler_find_job_return(batch):
+@@ -322,9 +309,7 @@ def test_batch__event_handler_find_job_return(batch):
+ def test_batch_run_next_end_batch_when_no_next(batch):
+ future = salt.ext.tornado.gen.Future()
+ future.set_result({})
+- with patch.object(
+- batch, "_get_next", return_value={}
+- ), patch.object(
++ with patch.object(batch, "_get_next", return_value={}), patch.object(
+ batch, "end_batch", return_value=future
+ ) as end_batch_mock:
+ batch.run_next()
+@@ -337,9 +322,7 @@ def test_batch_find_job(batch):
+ batch.minions = {"foo", "bar"}
+ with patch("salt.ext.tornado.gen.sleep", return_value=future), patch.object(
+ batch, "check_find_job", return_value=future
+- ) as check_find_job_mock, patch.object(
+- batch, "jid_gen", return_value="1236"
+- ):
++ ) as check_find_job_mock, patch.object(batch, "jid_gen", return_value="1236"):
+ batch.events_channel.local_client.run_job_async.return_value = future
+ batch.find_job({"foo", "bar"})
+ assert check_find_job_mock.call_args[0] == (
+@@ -355,9 +338,7 @@ def test_batch_find_job_with_done_minions(batch):
+ batch.minions = {"foo", "bar"}
+ with patch("salt.ext.tornado.gen.sleep", return_value=future), patch.object(
+ batch, "check_find_job", return_value=future
+- ) as check_find_job_mock, patch.object(
+- batch, "jid_gen", return_value="1236"
+- ):
++ ) as check_find_job_mock, patch.object(batch, "jid_gen", return_value="1236"):
+ batch.events_channel.local_client.run_job_async.return_value = future
+ batch.find_job({"foo", "bar"})
+ assert check_find_job_mock.call_args[0] == (
+--
+2.48.1
+
diff --git a/fix-tests-failures-after-repo.saltproject.io-depreca.patch b/fix-tests-failures-after-repo.saltproject.io-depreca.patch
new file mode 100644
index 0000000..8cdad99
--- /dev/null
+++ b/fix-tests-failures-after-repo.saltproject.io-depreca.patch
@@ -0,0 +1,518 @@
+From a630c6a707a1d5227b4a1fa8f0f751fefd3ef47f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
+
+Date: Wed, 19 Feb 2025 13:56:01 +0000
+Subject: [PATCH] Fix tests failures after "repo.saltproject.io"
+ deprecation (#704)
+
+* Use broadcom.com instead of repo.saltproject.io for test_cp
+
+* Change repo.saltproject.io to new url
+
+---------
+
+Co-authored-by: Daniel A. Wozniak
+Co-authored-by: twangboy
+---
+ README.rst | 4 +-
+ doc/_themes/saltstack2/layout.html | 15 +--
+ doc/conf.py | 12 +-
+ doc/ref/configuration/delta_proxy.rst | 6 +-
+ doc/topics/cloud/windows.rst | 2 +-
+ pkg/tests/support/helpers.py | 115 ++++++++++++------
+ salt/modules/saltutil.py | 4 +-
+ salt/runners/manage.py | 5 +-
+ salt/states/pkgrepo.py | 6 +-
+ tests/integration/modules/test_cp.py | 41 ++++---
+ tests/pytests/functional/modules/test_pkg.py | 8 +-
+ .../functional/states/pkgrepo/test_debian.py | 6 +-
+ .../integration/netapi/test_ssh_client.py | 3 +-
+ tests/support/win_installer.py | 1 +
+ 14 files changed, 135 insertions(+), 93 deletions(-)
+
+diff --git a/README.rst b/README.rst
+index f5121f1a74..77806aa14a 100644
+--- a/README.rst
++++ b/README.rst
+@@ -93,7 +93,9 @@ for more information.
+
+ To download and install Salt, see:
+ * `The Salt install guide `_
+-* `Salt Project repository `_
++* `Salt Project repository `_
++* `Salt Project debian repository `_
++* `Salt Project redhat repository `_
+
+
+ Technical support
+diff --git a/doc/_themes/saltstack2/layout.html b/doc/_themes/saltstack2/layout.html
+index 04bff89e1f..83918a7fb3 100644
+--- a/doc/_themes/saltstack2/layout.html
++++ b/doc/_themes/saltstack2/layout.html
+@@ -157,16 +157,11 @@
+
+
+
+diff --git a/doc/conf.py b/doc/conf.py
+index 653d912c20..24420d402e 100644
+--- a/doc/conf.py
++++ b/doc/conf.py
+@@ -178,17 +178,17 @@ rst_prolog = """\
+ .. |windownload| raw:: html
+
+ Python3 x86: Salt-Minion-{release}-x86-Setup.exe
+- | md5
++ href="https://packages.broadcom.com/artifactory/saltproject-generic/windows/{release}/Salt-Minion-{release}-Py3-x86-Setup.exe">Salt-Minion-{release}-x86-Setup.exe
++ | md5
+
+ Python3 AMD64: Salt-Minion-{release}-AMD64-Setup.exe
+- | md5
++ href="https://packages.broadcom.com/artifactory/saltproject-generic/windows/{release}/Salt-Minion-{release}-Py3-AMD64-Setup.exe">Salt-Minion-{release}-AMD64-Setup.exe
++ | md5
+
+ .. |osxdownloadpy3| raw:: html
+
+- x86_64: salt-{release}-py3-x86_64.pkg
+- | md5
++ x86_64: salt-{release}-py3-x86_64.pkg
++ | md5
+
+ """.format(
+ release=stripped_release
+diff --git a/doc/ref/configuration/delta_proxy.rst b/doc/ref/configuration/delta_proxy.rst
+index be1831da39..bce5f821c9 100644
+--- a/doc/ref/configuration/delta_proxy.rst
++++ b/doc/ref/configuration/delta_proxy.rst
+@@ -146,10 +146,8 @@ Before installing the delta proxy minion, ensure that:
+ Install or upgrade Salt
+ -----------------------
+ Ensure your Salt masters are running at least Salt version 3004. For instructions
+-on installing or upgrading Salt, see `repo.saltproject.io
+-`_. For RedHat systems, see `Install or Upgrade Salt
+-`_.
+-
++on installing or upgrading Salt, see the `Salt install guide
++`_.
+
+
+ .. _delta-proxy-install:
+diff --git a/doc/topics/cloud/windows.rst b/doc/topics/cloud/windows.rst
+index 9dfdde6db5..79d6665a5a 100644
+--- a/doc/topics/cloud/windows.rst
++++ b/doc/topics/cloud/windows.rst
+@@ -62,7 +62,7 @@ from saltstack.com:
+
+ * `SaltStack Download Area`__
+
+-.. __: https://repo.saltproject.io/windows/
++.. __: https://packages.broadcom.com/artifactory/saltproject-generic/windows/
+
+ .. _new-pywinrm:
+
+diff --git a/pkg/tests/support/helpers.py b/pkg/tests/support/helpers.py
+index 90abf8b88e..ce23f699b6 100644
+--- a/pkg/tests/support/helpers.py
++++ b/pkg/tests/support/helpers.py
+@@ -636,8 +636,7 @@ class SaltPkgInstall:
+
+ def install_previous(self):
+ """
+- Install previous version. This is used for
+- upgrade tests.
++ Install previous version. This is used for upgrade tests.
+ """
+ major_ver = self.major
+ minor_ver = self.minor
+@@ -648,16 +647,12 @@ class SaltPkgInstall:
+ distro_name = self.distro_name
+ if distro_name == "centos" or distro_name == "fedora":
+ distro_name = "redhat"
+- root_url = "salt/py3/"
+- if self.classic:
+- root_url = "py3/"
++ root_url = "https://packages.broadcom.com/artifactory"
+
+ if self.distro_name in ["redhat", "centos", "amazon", "fedora", "vmware"]:
+ for fp in pathlib.Path("/etc", "yum.repos.d").glob("epel*"):
+ fp.unlink()
+- gpg_key = "SALTSTACK-GPG-KEY.pub"
+- if self.distro_version == "9":
+- gpg_key = "SALTSTACK-GPG-KEY2.pub"
++
+ if platform.is_aarch64():
+ arch = "aarch64"
+ else:
+@@ -694,46 +689,86 @@ class SaltPkgInstall:
+ arch = "arm64"
+ else:
+ arch = "amd64"
++
+ pathlib.Path("/etc/apt/keyrings").mkdir(parents=True, exist_ok=True)
++ gpg_full_path = "/etc/apt/keyrings/salt-archive-keyring.gpg"
++
++ # download the gpg pub key
+ download_file(
+- f"https://repo.saltproject.io/{root_url}{distro_name}/{self.distro_version}/{arch}/{major_ver}/salt-archive-keyring.gpg",
+- "/etc/apt/keyrings/salt-archive-keyring.gpg",
++ f"{root_url}/api/security/keypair/SaltProjectKey/public",
++ f"{gpg_full_path}",
+ )
+- with open(
++ with salt.utils.files.fopen(
+ pathlib.Path("/etc", "apt", "sources.list.d", "salt.list"), "w"
+ ) as fp:
+ fp.write(
+- f"deb [signed-by=/etc/apt/keyrings/salt-archive-keyring.gpg arch={arch}] "
+- f"https://repo.saltproject.io/{root_url}{distro_name}/{self.distro_version}/{arch}/{major_ver} {self.distro_codename} main"
++ f"deb [signed-by={gpg_full_path} arch={arch}] "
++ f"{root_url}/saltproject-deb/ {self.distro_codename} main"
+ )
+- ret = self.proc.run(self.pkg_mngr, "update")
+- self._check_retcode(ret)
+- ret = self.proc.run(
+- self.pkg_mngr,
+- "install",
+- *self.salt_pkgs,
+- "-y",
+- )
+ self._check_retcode(ret)
++
++ cmd = [self.pkg_mngr, "install", *self.salt_pkgs, "-y"]
++
++ if downgrade:
++ pref_file = pathlib.Path("/etc", "apt", "preferences.d", "salt.pref")
++ pref_file.parent.mkdir(exist_ok=True)
++ # TODO: There's probably something I should put in here to say what version
++ # TODO: But maybe that's done elsewhere, hopefully in self.salt_pkgs
++ pref_file.write_text(
++ textwrap.dedent(
++ f"""\
++ Package: salt*
++ Pin: origin "{root_url}/saltproject-deb"
++ Pin-Priority: 1001
++ """
++ ),
++ encoding="utf-8",
++ )
++ cmd.append("--allow-downgrades")
++ env = os.environ.copy()
++ env["DEBIAN_FRONTEND"] = "noninteractive"
++ extra_args = [
++ "-o",
++ "DPkg::Options::=--force-confdef",
++ "-o",
++ "DPkg::Options::=--force-confold",
++ ]
++ self.proc.run(self.pkg_mngr, "update", *extra_args, env=env)
++
++ cmd.extend(extra_args)
++
++ ret = self.proc.run(*cmd, env=env)
++ # Pre-relenv packages down get downgraded to cleanly programmatically
++ # They work manually, and the install tests after downgrades will catch problems with the install
++ # Let's not check the returncode if this is the case
++ if not (
++ downgrade
++ and packaging.version.parse(self.prev_version)
++ < packaging.version.parse("3006.0")
++ ):
++ self._check_retcode(ret)
++ if downgrade:
++ pref_file.unlink()
+ self.stop_services()
+ elif platform.is_windows():
+ self.onedir = True
+ self.installer_pkg = True
+ self.bin_dir = self.install_dir / "bin"
+- self.run_root = self.bin_dir / f"salt.exe"
+- self.ssm_bin = self.bin_dir / "ssm.exe"
+- if self.file_ext == "msi":
+- self.ssm_bin = self.install_dir / "ssm.exe"
++ self.run_root = self.bin_dir / "salt.exe"
++ self.ssm_bin = self.install_dir / "ssm.exe"
+
+- if not self.classic:
+- win_pkg = f"salt-{full_version}-windows-amd64.{self.file_ext}"
+- win_pkg_url = f"https://repo.saltproject.io/salt/py3/windows/{full_version}/{win_pkg}"
++ if self.file_ext == "exe":
++ win_pkg = (
++ f"Salt-Minion-{self.prev_version}-Py3-AMD64-Setup.{self.file_ext}"
++ )
++ elif self.file_ext == "msi":
++ win_pkg = f"Salt-Minion-{self.prev_version}-Py3-AMD64.{self.file_ext}"
+ else:
+- if self.file_ext == "msi":
+- win_pkg = f"Salt-Minion-{min_ver}-1-Py3-AMD64.{self.file_ext}"
+- elif self.file_ext == "exe":
+- win_pkg = f"Salt-Minion-{min_ver}-1-Py3-AMD64-Setup.{self.file_ext}"
+- win_pkg_url = f"https://repo.saltproject.io/windows/{win_pkg}"
++ log.debug(f"Unknown windows file extension: {self.file_ext}")
++
++ win_pkg_url = (
++ f"{root_url}/saltproject-generic/windows/{major_ver}/{win_pkg}"
++ )
+ pkg_path = pathlib.Path(r"C:\TEMP", win_pkg)
+ pkg_path.parent.mkdir(exist_ok=True)
+ ret = requests.get(win_pkg_url)
+@@ -763,12 +798,16 @@ class SaltPkgInstall:
+ self._install_system_service()
+
+ elif platform.is_darwin():
+- if self.classic:
+- mac_pkg = f"salt-{min_ver}.{minor_ver}-1-py3-x86_64.pkg"
+- mac_pkg_url = f"https://repo.saltproject.io/osx/{mac_pkg}"
++ if relenv and platform.is_aarch64():
++ arch = "arm64"
++ elif platform.is_aarch64() and self.classic:
++ arch = "arm64"
+ else:
+- mac_pkg = f"salt-{min_ver}.{minor_ver}-1-macos-x86_64.pkg"
+- mac_pkg_url = f"https://repo.saltproject.io/salt/py3/macos/{major_ver}.{minor_ver}-1/{mac_pkg}"
++ arch = "x86_64"
++
++ mac_pkg = f"salt-{self.prev_version}-py3-{arch}.pkg"
++ mac_pkg_url = f"{root_url}/saltproject-generic/macos/{major_ver}/{mac_pkg}"
++
+ mac_pkg_path = f"/tmp/{mac_pkg}"
+ if not os.path.exists(mac_pkg_path):
+ download_file(
+diff --git a/salt/modules/saltutil.py b/salt/modules/saltutil.py
+index a692c3f34d..320b9c34fa 100644
+--- a/salt/modules/saltutil.py
++++ b/salt/modules/saltutil.py
+@@ -128,8 +128,8 @@ def _sync(form, saltenv=None, extmod_whitelist=None, extmod_blacklist=None):
+ def update(version=None):
+ """
+ Update the salt minion from the URL defined in opts['update_url']
+- VMware, Inc provides the latest builds here:
+- update_url: https://repo.saltproject.io/windows/
++ Broadcom, Inc provides the latest builds here:
++ update_url: https://packages.broadcom.com/artifactory/saltproject-generic/windows/
+
+ Be aware that as of 2014-8-11 there's a bug in esky such that only the
+ latest version available in the update_url can be downloaded and installed.
+diff --git a/salt/runners/manage.py b/salt/runners/manage.py
+index 9dc67ed728..81197ca41f 100644
+--- a/salt/runners/manage.py
++++ b/salt/runners/manage.py
+@@ -772,7 +772,7 @@ def bootstrap_psexec(
+
+ installer_url
+ URL of minion installer executable. Defaults to the latest version from
+- https://repo.saltproject.io/windows/
++ https://packages.broadcom.com/artifactory/saltproject-generic/windows/
+
+ username
+ Optional user name for login on remote computer.
+@@ -790,6 +790,9 @@ def bootstrap_psexec(
+ salt-run manage.bootstrap_psexec hosts='host1,host2' installer_url='http://exampledomain/salt-installer.exe'
+ """
+
++ # TODO: Need to make this gets the latest version from the new repo location
++ # TODO: Similar to tests/support/win_installer.py
++ # TODO: Maybe need to move that ^^^^ to a salt util
+ if not installer_url:
+ base_url = "https://repo.saltproject.io/windows/"
+ source = urllib.request.urlopen(base_url).read()
+diff --git a/salt/states/pkgrepo.py b/salt/states/pkgrepo.py
+index f041644287..4ef5fd9c2f 100644
+--- a/salt/states/pkgrepo.py
++++ b/salt/states/pkgrepo.py
+@@ -99,17 +99,17 @@ Using ``aptkey: False`` with ``key_url`` example:
+
+ .. code-block:: yaml
+
+- deb [signed-by=/etc/apt/keyrings/salt-archive-keyring.gpg arch=amd64] https://repo.saltproject.io/py3/ubuntu/18.04/amd64/latest bionic main:
++ deb [signed-by=/etc/apt/keyrings/salt-archive-keyring.gpg arch=amd64] https://packages.broadcom.com/artifactory/saltproject-deb/ bionic main:
+ pkgrepo.managed:
+ - file: /etc/apt/sources.list.d/salt.list
+- - key_url: https://repo.saltproject.io/py3/ubuntu/18.04/amd64/latest/salt-archive-keyring.gpg
++ - key_url: https://packages.broadcom.com/artifactory/api/security/keypair/SaltProjectKey/public
+ - aptkey: False
+
+ Using ``aptkey: False`` with ``keyserver`` and ``keyid``:
+
+ .. code-block:: yaml
+
+- deb [signed-by=/etc/apt/keyrings/salt-archive-keyring.gpg arch=amd64] https://repo.saltproject.io/py3/ubuntu/18.04/amd64/latest bionic main:
++ deb [signed-by=/etc/apt/keyrings/salt-archive-keyring.gpg arch=amd64] https://packages.broadcom.com/artifactory/saltproject-deb/ bionic main:
+ pkgrepo.managed:
+ - file: /etc/apt/sources.list.d/salt.list
+ - keyserver: keyserver.ubuntu.com
+diff --git a/tests/integration/modules/test_cp.py b/tests/integration/modules/test_cp.py
+index cd3e4c2f5a..d417f90ddc 100644
+--- a/tests/integration/modules/test_cp.py
++++ b/tests/integration/modules/test_cp.py
+@@ -231,12 +231,15 @@ class CPModuleTest(ModuleCase):
+ """
+ cp.get_url with https:// source given
+ """
+- self.run_function("cp.get_url", ["https://repo.saltproject.io/index.html", tgt])
++ self.run_function(
++ "cp.get_url",
++ ["https://packages.broadcom.com/artifactory/saltproject-generic/", tgt],
++ )
+ with salt.utils.files.fopen(tgt, "r") as instructions:
+ data = salt.utils.stringutils.to_unicode(instructions.read())
+- self.assertIn("Salt Project", data)
+- self.assertIn("Package", data)
+- self.assertIn("Repo", data)
++ self.assertIn("Index of saltproject", data)
++ self.assertIn("onedir", data)
++ self.assertIn("Artifactory Online Server", data)
+ self.assertNotIn("AYBABTU", data)
+
+ @pytest.mark.slow_test
+@@ -245,14 +248,15 @@ class CPModuleTest(ModuleCase):
+ cp.get_url with https:// source given and destination omitted.
+ """
+ ret = self.run_function(
+- "cp.get_url", ["https://repo.saltproject.io/index.html"]
++ "cp.get_url",
++ ["https://packages.broadcom.com/artifactory/saltproject-generic/"],
+ )
+
+ with salt.utils.files.fopen(ret, "r") as instructions:
+ data = salt.utils.stringutils.to_unicode(instructions.read())
+- self.assertIn("Salt Project", data)
+- self.assertIn("Package", data)
+- self.assertIn("Repo", data)
++ self.assertIn("Index of saltproject", data)
++ self.assertIn("onedir", data)
++ self.assertIn("Artifactory Online Server", data)
+ self.assertNotIn("AYBABTU", data)
+
+ @pytest.mark.slow_test
+@@ -266,16 +270,19 @@ class CPModuleTest(ModuleCase):
+ tgt = None
+ while time.time() - start <= timeout:
+ ret = self.run_function(
+- "cp.get_url", ["https://repo.saltproject.io/index.html", tgt]
++ "cp.get_url",
++ ["https://packages.broadcom.com/artifactory/saltproject-generic/", tgt],
+ )
+ if ret.find("HTTP 599") == -1:
+ break
+ time.sleep(sleep)
+ if ret.find("HTTP 599") != -1:
+- raise Exception("https://repo.saltproject.io/index.html returned 599 error")
+- self.assertIn("Salt Project", ret)
+- self.assertIn("Package", ret)
+- self.assertIn("Repo", ret)
++ raise Exception(
++ "https://packages.broadcom.com/artifactory/saltproject-generic/ returned 599 error"
++ )
++ self.assertIn("Index of saltproject", ret)
++ self.assertIn("onedir", ret)
++ self.assertIn("Artifactory Online Server", ret)
+ self.assertNotIn("AYBABTU", ret)
+
+ @pytest.mark.slow_test
+@@ -344,11 +351,11 @@ class CPModuleTest(ModuleCase):
+ """
+ cp.get_file_str with https:// source given
+ """
+- src = "https://repo.saltproject.io/index.html"
++ src = "https://packages.broadcom.com/artifactory/saltproject-generic/"
+ ret = self.run_function("cp.get_file_str", [src])
+- self.assertIn("Salt Project", ret)
+- self.assertIn("Package", ret)
+- self.assertIn("Repo", ret)
++ self.assertIn("Index of saltproject", ret)
++ self.assertIn("onedir", ret)
++ self.assertIn("Artifactory Online Server", ret)
+ self.assertNotIn("AYBABTU", ret)
+
+ @pytest.mark.slow_test
+diff --git a/tests/pytests/functional/modules/test_pkg.py b/tests/pytests/functional/modules/test_pkg.py
+index 82d0801965..addb3da3d1 100644
+--- a/tests/pytests/functional/modules/test_pkg.py
++++ b/tests/pytests/functional/modules/test_pkg.py
+@@ -130,12 +130,8 @@ def test_mod_del_repo(grains, modules, refresh_db):
+ elif grains["os_family"] == "RedHat":
+ repo = "saltstack"
+ name = "SaltStack repo for RHEL/CentOS {}".format(grains["osmajorrelease"])
+- baseurl = "https://repo.saltproject.io/py3/redhat/{}/x86_64/latest/".format(
+- grains["osmajorrelease"]
+- )
+- gpgkey = "https://repo.saltproject.io/py3/redhat/{}/x86_64/latest/SALTSTACK-GPG-KEY.pub".format(
+- grains["osmajorrelease"]
+- )
++ baseurl = "https://packages.broadcom.com/artifactory/saltproject-rpm/"
++ gpgkey = "https://packages.broadcom.com/artifactory/api/security/keypair/SaltProjectKey/public"
+ gpgcheck = 1
+ enabled = 1
+ ret = modules.pkg.mod_repo(
+diff --git a/tests/pytests/functional/states/pkgrepo/test_debian.py b/tests/pytests/functional/states/pkgrepo/test_debian.py
+index 7bda100b63..307fcb5819 100644
+--- a/tests/pytests/functional/states/pkgrepo/test_debian.py
++++ b/tests/pytests/functional/states/pkgrepo/test_debian.py
+@@ -616,8 +616,8 @@ class Repo:
+ @alt_repo.default
+ def _default_alt_repo(self):
+ """
+- Use an alternative repo, packages do not
+- exist for the OS on repo.saltproject.io
++ Use an alternative repo, packages do not exist for the OS on
++ packages.broadcom.com
+ """
+ if (
+ self.grains["osfullname"] == "Ubuntu"
+@@ -777,7 +777,7 @@ def test_adding_repo_file_signedby_alt_file(pkgrepo, states, repo):
+ assert repo.repo_content in ret.comment
+
+ key_file = repo.key_file.parent / "salt-alt-key.gpg"
+- repo_content = "deb [arch=amd64 signed-by={}] https://repo.saltproject.io/py3/debian/10/amd64/latest buster main".format(
++ repo_content = "deb [arch=amd64 signed-by={}] https://packages.broadcom.com/artifactory/saltproject-deb/ buster main".format(
+ str(key_file)
+ )
+ ret = states.pkgrepo.managed(
+diff --git a/tests/pytests/integration/netapi/test_ssh_client.py b/tests/pytests/integration/netapi/test_ssh_client.py
+index 457c151c94..7dd540d9b9 100644
+--- a/tests/pytests/integration/netapi/test_ssh_client.py
++++ b/tests/pytests/integration/netapi/test_ssh_client.py
+@@ -149,7 +149,8 @@ def test_shell_inject_ssh_priv(
+ """
+ # ZDI-CAN-11143
+ path = tmp_path / "test-11143"
+- tgts = ["repo.saltproject.io", "www.zerodayinitiative.com"]
++ tgts = ["packages.broadcom.com", "www.zerodayinitiative.com"]
++ ret = None
+ for tgt in tgts:
+ low = {
+ "roster": "cache",
+diff --git a/tests/support/win_installer.py b/tests/support/win_installer.py
+index 6a2f387dc8..d67105e8a0 100644
+--- a/tests/support/win_installer.py
++++ b/tests/support/win_installer.py
+@@ -10,6 +10,7 @@
+ """
+
+ import hashlib
++from html.parser import HTMLParser
+
+ import requests
+
+--
+2.48.1
+
diff --git a/fixed-file-client-private-attribute-reference-on-sal.patch b/fixed-file-client-private-attribute-reference-on-sal.patch
new file mode 100644
index 0000000..11ecfae
--- /dev/null
+++ b/fixed-file-client-private-attribute-reference-on-sal.patch
@@ -0,0 +1,81 @@
+From 1772da828f40e36d2a9eceb7055a1fa1a2257830 Mon Sep 17 00:00:00 2001
+From: Georg
+Date: Fri, 21 Feb 2025 10:23:38 +0000
+Subject: [PATCH] Fixed file client private attribute reference on
+ `SaltMakoTemplateLookup` (#694)
+
+Fixes #64280
+
+Signed-off-by: Pedro Algarvio
+(cherry picked from commit 560ab52ccf94c7974d5a418dfbba7409e0493066)
+
+Co-authored-by: Pedro Algarvio
+---
+ changelog/64280.fixed.md | 1 +
+ salt/utils/mako.py | 6 ++++--
+ tests/pytests/unit/utils/test_mako.py | 28 +++++++++++++++++++++++++++
+ 3 files changed, 33 insertions(+), 2 deletions(-)
+ create mode 100644 changelog/64280.fixed.md
+ create mode 100644 tests/pytests/unit/utils/test_mako.py
+
+diff --git a/changelog/64280.fixed.md b/changelog/64280.fixed.md
+new file mode 100644
+index 0000000000..5a9b905dd0
+--- /dev/null
++++ b/changelog/64280.fixed.md
+@@ -0,0 +1 @@
++Fixed file client private attribute reference on `SaltMakoTemplateLookup`
+diff --git a/salt/utils/mako.py b/salt/utils/mako.py
+index 037d5d86de..4397ae8cc7 100644
+--- a/salt/utils/mako.py
++++ b/salt/utils/mako.py
+@@ -99,8 +99,10 @@ if HAS_MAKO:
+ )
+
+ def destroy(self):
+- if self.client:
++ if self._file_client:
++ file_client = self._file_client
++ self._file_client = None
+ try:
+- self.client.destroy()
++ file_client.destroy()
+ except AttributeError:
+ pass
+diff --git a/tests/pytests/unit/utils/test_mako.py b/tests/pytests/unit/utils/test_mako.py
+new file mode 100644
+index 0000000000..952cf44652
+--- /dev/null
++++ b/tests/pytests/unit/utils/test_mako.py
+@@ -0,0 +1,28 @@
++import pytest
++
++from tests.support.mock import Mock, call, patch
++
++pytest.importorskip("mako")
++
++# This import needs to be after the above importorskip so that no ImportError
++# is raised if Mako is not installed
++from salt.utils.mako import SaltMakoTemplateLookup
++
++
++def test_mako_template_lookup(minion_opts):
++ """
++ The shudown method can be called without raising an exception when the
++ file_client does not have a destroy method
++ """
++ # Test SaltCacheLoader creating and destroying the file client created
++ file_client = Mock()
++ with patch("salt.fileclient.get_file_client", return_value=file_client):
++ loader = SaltMakoTemplateLookup(minion_opts)
++ assert loader._file_client is None
++ assert loader.file_client() is file_client
++ assert loader._file_client is file_client
++ try:
++ loader.destroy()
++ except AttributeError:
++ pytest.fail("Regression when calling SaltMakoTemplateLookup.destroy()")
++ assert file_client.mock_calls == [call.destroy()]
+--
+2.48.1
+
diff --git a/make-_auth-calls-visible-with-master-stats-696.patch b/make-_auth-calls-visible-with-master-stats-696.patch
new file mode 100644
index 0000000..3f90b08
--- /dev/null
+++ b/make-_auth-calls-visible-with-master-stats-696.patch
@@ -0,0 +1,142 @@
+From 32099b97c2fa549cb050d3ae618b5200c07328c8 Mon Sep 17 00:00:00 2001
+From: Victor Zhestkov
+Date: Fri, 21 Feb 2025 11:59:00 +0100
+Subject: [PATCH] Make `_auth` calls visible with master stats (#696)
+
+* Put _auth calls to the master stats
+
+* Add _auth master stats tests
+
+* test small fix
+---
+ salt/channel/server.py | 9 ++++--
+ salt/master.py | 5 ++++
+ tests/pytests/unit/channel/__init__.py | 0
+ tests/pytests/unit/channel/test_server.py | 34 +++++++++++++++++++++++
+ tests/pytests/unit/test_master.py | 25 +++++++++++++++++
+ 5 files changed, 70 insertions(+), 3 deletions(-)
+ create mode 100644 tests/pytests/unit/channel/__init__.py
+ create mode 100644 tests/pytests/unit/channel/test_server.py
+
+diff --git a/salt/channel/server.py b/salt/channel/server.py
+index f1b6f701a9..59da3a2dc2 100644
+--- a/salt/channel/server.py
++++ b/salt/channel/server.py
+@@ -9,6 +9,7 @@ import hashlib
+ import logging
+ import os
+ import shutil
++import time
+
+ import salt.crypt
+ import salt.ext.tornado.gen
+@@ -149,9 +150,11 @@ class ReqServerChannel:
+ # intercept the "_auth" commands, since the main daemon shouldn't know
+ # anything about our key auth
+ if payload["enc"] == "clear" and payload.get("load", {}).get("cmd") == "_auth":
+- raise salt.ext.tornado.gen.Return(
+- self._auth(payload["load"], sign_messages)
+- )
++ start = time.time()
++ ret = self._auth(payload["load"], sign_messages)
++ if self.opts.get("master_stats", False):
++ yield self.payload_handler({"cmd": "_auth", "_start": start})
++ raise salt.ext.tornado.gen.Return(ret)
+
+ nonce = None
+ if version > 1:
+diff --git a/salt/master.py b/salt/master.py
+index 49cfb68860..c0cd9a366b 100644
+--- a/salt/master.py
++++ b/salt/master.py
+@@ -1036,6 +1036,11 @@ class MWorker(salt.utils.process.SignalHandlingProcess):
+
+ :param dict payload: The payload route to the appropriate handler
+ """
++ if payload.get("cmd") == "_auth":
++ if self.opts["master_stats"]:
++ self.stats["_auth"]["runs"] += 1
++ self._post_stats(payload["_start"], "_auth")
++ return
+ key = payload["enc"]
+ load = payload["load"]
+ if key == "aes":
+diff --git a/tests/pytests/unit/channel/__init__.py b/tests/pytests/unit/channel/__init__.py
+new file mode 100644
+index 0000000000..e69de29bb2
+diff --git a/tests/pytests/unit/channel/test_server.py b/tests/pytests/unit/channel/test_server.py
+new file mode 100644
+index 0000000000..3fa5d94bea
+--- /dev/null
++++ b/tests/pytests/unit/channel/test_server.py
+@@ -0,0 +1,34 @@
++import time
++
++import pytest
++
++import salt.channel.server as server
++import salt.ext.tornado.gen
++from tests.support.mock import MagicMock, patch
++
++
++def test__auth_cmd_stats_passing():
++ req_server_channel = server.ReqServerChannel({"master_stats": True}, None)
++
++ fake_ret = {"enc": "clear", "load": b"FAKELOAD"}
++
++ def _auth_mock(*_, **__):
++ time.sleep(0.03)
++ return fake_ret
++
++ future = salt.ext.tornado.gen.Future()
++ future.set_result({})
++
++ with patch.object(req_server_channel, "_auth", _auth_mock):
++ req_server_channel.payload_handler = MagicMock(return_value=future)
++ req_server_channel.handle_message(
++ {"enc": "clear", "load": {"cmd": "_auth", "id": "minion"}}
++ )
++ cur_time = time.time()
++ req_server_channel.payload_handler.assert_called_once()
++ assert req_server_channel.payload_handler.call_args[0][0]["cmd"] == "_auth"
++ auth_call_duration = (
++ cur_time - req_server_channel.payload_handler.call_args[0][0]["_start"]
++ )
++ assert auth_call_duration >= 0.03
++ assert auth_call_duration < 0.05
+diff --git a/tests/pytests/unit/test_master.py b/tests/pytests/unit/test_master.py
+index 679229066d..7fccb24d73 100644
+--- a/tests/pytests/unit/test_master.py
++++ b/tests/pytests/unit/test_master.py
+@@ -282,3 +282,28 @@ def test_syndic_return_cache_dir_creation_traversal(encrypted_requests):
+ )
+ assert not (cachedir / "syndics").exists()
+ assert not (cachedir / "mamajama").exists()
++
++
++def test_collect__auth_to_master_stats():
++ """
++ Check if master stats is collecting _auth calls while not calling neither _handle_aes nor _handle_clear
++ """
++ opts = {
++ "master_stats": True,
++ "master_stats_event_iter": 10,
++ }
++ req_channel_mock = MagicMock()
++ mworker = salt.master.MWorker(opts, {}, {}, [req_channel_mock])
++ with patch.object(mworker, "_handle_aes") as handle_aes_mock, patch.object(
++ mworker, "_handle_clear"
++ ) as handle_clear_mock:
++ mworker._handle_payload({"cmd": "_auth", "_start": time.time() - 0.02})
++ assert mworker.stats["_auth"]["runs"] == 1
++ assert mworker.stats["_auth"]["mean"] >= 0.02
++ assert mworker.stats["_auth"]["mean"] < 0.04
++ mworker._handle_payload({"cmd": "_auth", "_start": time.time() - 0.02})
++ assert mworker.stats["_auth"]["runs"] == 2
++ assert mworker.stats["_auth"]["mean"] >= 0.02
++ assert mworker.stats["_auth"]["mean"] < 0.04
++ handle_aes_mock.assert_not_called()
++ handle_clear_mock.assert_not_called()
+--
+2.48.1
+
diff --git a/repair-fstab_present-test-mode-702.patch b/repair-fstab_present-test-mode-702.patch
new file mode 100644
index 0000000..5c67e09
--- /dev/null
+++ b/repair-fstab_present-test-mode-702.patch
@@ -0,0 +1,69 @@
+From 73d18711314738796e802a6d929f4b609cee1f67 Mon Sep 17 00:00:00 2001
+From: Georg
+Date: Fri, 21 Feb 2025 10:26:25 +0000
+Subject: [PATCH] Repair fstab_present test mode (#702)
+
+Return no pending changes if the configuration already matches.
+
+Signed-off-by: Georg Pfuetzenreuter
+(cherry picked from commit fc7ed2b53152ab255d7763f200e8d28d526c5e52)
+---
+ changelog/67065.fixed.md | 1 +
+ salt/states/mount.py | 1 +
+ tests/pytests/unit/states/test_mount.py | 6 +++---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ create mode 100644 changelog/67065.fixed.md
+
+diff --git a/changelog/67065.fixed.md b/changelog/67065.fixed.md
+new file mode 100644
+index 0000000000..7b210dc297
+--- /dev/null
++++ b/changelog/67065.fixed.md
+@@ -0,0 +1 @@
++Repaired mount.fstab_present always returning pending changes
+diff --git a/salt/states/mount.py b/salt/states/mount.py
+index 36b9a16b5d..97dddbe3b0 100644
+--- a/salt/states/mount.py
++++ b/salt/states/mount.py
+@@ -1228,6 +1228,7 @@ def fstab_present(
+ if out == "present":
+ msg = "{} entry is already in {}."
+ ret["comment"].append(msg.format(fs_file, config))
++ ret["result"] = True
+ elif out == "new":
+ msg = "{} entry will be written in {}."
+ ret["comment"].append(msg.format(fs_file, config))
+diff --git a/tests/pytests/unit/states/test_mount.py b/tests/pytests/unit/states/test_mount.py
+index 5e4d5274e8..382fe6d0e8 100644
+--- a/tests/pytests/unit/states/test_mount.py
++++ b/tests/pytests/unit/states/test_mount.py
+@@ -701,7 +701,7 @@ def test_fstab_present_macos_test_present():
+ """
+ ret = {
+ "name": "/dev/sda1",
+- "result": None,
++ "result": True,
+ "changes": {},
+ "comment": ["/home entry is already in /etc/auto_salt."],
+ }
+@@ -730,7 +730,7 @@ def test_fstab_present_aix_test_present():
+ """
+ ret = {
+ "name": "/dev/sda1",
+- "result": None,
++ "result": True,
+ "changes": {},
+ "comment": ["/home entry is already in /etc/filesystems."],
+ }
+@@ -761,7 +761,7 @@ def test_fstab_present_test_present():
+ """
+ ret = {
+ "name": "/dev/sda1",
+- "result": None,
++ "result": True,
+ "changes": {},
+ "comment": ["/home entry is already in /etc/fstab."],
+ }
+--
+2.48.1
+
diff --git a/repair-virt_query-outputter-655.patch b/repair-virt_query-outputter-655.patch
new file mode 100644
index 0000000..361e2a8
--- /dev/null
+++ b/repair-virt_query-outputter-655.patch
@@ -0,0 +1,304 @@
+From 325506774381cc8edadee9b2f43fd6733d4f9edb Mon Sep 17 00:00:00 2001
+From: Georg
+Date: Fri, 21 Feb 2025 12:40:45 +0000
+Subject: [PATCH] Repair virt_query outputter (#655)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+* Repair virt_query outputter
+
+Existing code was not functional.
+Only return if a dictionary is passed and reference the correct
+data.
+
+Signed-off-by: Georg Pfuetzenreuter
+(cherry picked from commit e3c365ad8f385121aa878950e13892d986d79656)
+
+* Facilitate block devices in virt_query outputter
+
+Disk data in Libvirt VMs does not contain a disk size if the disk
+references a block device.
+Skip the field for such disks instead of failing with a key error.
+
+Signed-off-by: Georg Pfuetzenreuter
+(cherry picked from commit ed73abd44117ad745e9c91f2b33caf04402b117c)
+
+* Add unit tests for virt_query outputter
+
+---------
+
+Co-authored-by: Pablo Suárez Hernández
+---
+ changelog/65841.fixed.md | 1 +
+ salt/output/virt_query.py | 64 +++----
+ tests/pytests/unit/output/test_virt_query.py | 176 +++++++++++++++++++
+ 3 files changed, 210 insertions(+), 31 deletions(-)
+ create mode 100644 changelog/65841.fixed.md
+ create mode 100644 tests/pytests/unit/output/test_virt_query.py
+
+diff --git a/changelog/65841.fixed.md b/changelog/65841.fixed.md
+new file mode 100644
+index 0000000000..7fb6336ea1
+--- /dev/null
++++ b/changelog/65841.fixed.md
+@@ -0,0 +1 @@
++Restore functionality of virt_query outputter and add support for block devices.
+diff --git a/salt/output/virt_query.py b/salt/output/virt_query.py
+index d20e6357e6..0f989fedfa 100644
+--- a/salt/output/virt_query.py
++++ b/salt/output/virt_query.py
+@@ -12,35 +12,37 @@ def output(data, **kwargs): # pylint: disable=unused-argument
+ Display output for the salt-run virt.query function
+ """
+ out = ""
+- for id_ in data["data"]:
+- out += "{}\n".format(id_)
+- for vm_ in data["data"][id_]["vm_info"]:
+- out += " {}\n".format(vm_)
+- vm_data = data[id_]["vm_info"][vm_]
+- if "cpu" in vm_data:
+- out += " CPU: {}\n".format(vm_data["cpu"])
+- if "mem" in vm_data:
+- out += " Memory: {}\n".format(vm_data["mem"])
+- if "state" in vm_data:
+- out += " State: {}\n".format(vm_data["state"])
+- if "graphics" in vm_data:
+- if vm_data["graphics"].get("type", "") == "vnc":
+- out += " Graphics: vnc - {}:{}\n".format(
+- id_, vm_data["graphics"]["port"]
+- )
+- if "disks" in vm_data:
+- for disk, d_data in vm_data["disks"].items():
+- out += " Disk - {}:\n".format(disk)
+- out += " Size: {}\n".format(d_data["disk size"])
+- out += " File: {}\n".format(d_data["file"])
+- out += " File Format: {}\n".format(d_data["file format"])
+- if "nics" in vm_data:
+- for mac in vm_data["nics"]:
+- out += " Nic - {}:\n".format(mac)
+- out += " Source: {}\n".format(
+- vm_data["nics"][mac]["source"][
+- next(iter(vm_data["nics"][mac]["source"].keys()))
+- ]
+- )
+- out += " Type: {}\n".format(vm_data["nics"][mac]["type"])
++ if isinstance(data, dict) and "event" in data:
++ for id_ in data["event"]["data"]:
++ out += "{}\n".format(id_)
++ for vm_ in data["event"]["data"][id_]["vm_info"]:
++ out += " {}\n".format(vm_)
++ vm_data = data["event"]["data"][id_]["vm_info"][vm_]
++ if "cpu" in vm_data:
++ out += " CPU: {}\n".format(vm_data["cpu"])
++ if "mem" in vm_data:
++ out += " Memory: {}\n".format(vm_data["mem"])
++ if "state" in vm_data:
++ out += " State: {}\n".format(vm_data["state"])
++ if "graphics" in vm_data:
++ if vm_data["graphics"].get("type", "") == "vnc":
++ out += " Graphics: vnc - {}:{}\n".format(
++ id_, vm_data["graphics"]["port"]
++ )
++ if "disks" in vm_data:
++ for disk, d_data in vm_data["disks"].items():
++ out += " Disk - {}:\n".format(disk)
++ if "disk size" in d_data:
++ out += " Size: {}\n".format(d_data["disk size"])
++ out += " File: {}\n".format(d_data["file"])
++ out += " File Format: {}\n".format(d_data["file format"])
++ if "nics" in vm_data:
++ for mac in vm_data["nics"]:
++ out += " NIC - {}:\n".format(mac)
++ out += " Source: {}\n".format(
++ vm_data["nics"][mac]["source"][
++ next(iter(vm_data["nics"][mac]["source"].keys()))
++ ]
++ )
++ out += " Type: {}\n".format(vm_data["nics"][mac]["type"])
+ return out
+diff --git a/tests/pytests/unit/output/test_virt_query.py b/tests/pytests/unit/output/test_virt_query.py
+new file mode 100644
+index 0000000000..3f8814ee26
+--- /dev/null
++++ b/tests/pytests/unit/output/test_virt_query.py
+@@ -0,0 +1,176 @@
++"""
++unittests for virt_query outputter
++"""
++
++import pytest
++
++import salt.output.virt_query as virt_query
++from tests.support.mock import patch
++
++
++@pytest.fixture
++def configure_loader_modules():
++ return {virt_query: {}}
++
++
++@pytest.fixture
++def data():
++ return {
++ "suffix": "progress",
++ "event": {
++ "data": {
++ "mysystem": {
++ "freecpu": 14,
++ "freemem": 29566.0,
++ "node_info": {
++ "cpucores": 8,
++ "cpumhz": 1089,
++ "cpumodel": "x86_64",
++ "cpus": 16,
++ "cputhreads": 2,
++ "numanodes": 1,
++ "phymemory": 30846,
++ "sockets": 1,
++ },
++ "vm_info": {
++ "vm1": {
++ "cpu": 2,
++ "cputime": 1214270000000,
++ "disks": {
++ "vda": {
++ "file": "default/vm1-main-disk",
++ "type": "disk",
++ "file format": "qcow2",
++ "virtual size": 214748364800,
++ "disk size": 1831731200,
++ "backing file": {
++ "file": "/var/lib/libvirt/images/sles15sp4o",
++ "file format": "qcow2",
++ },
++ },
++ "hdd": {
++ "file": "default/vm1-cloudinit-disk",
++ "type": "cdrom",
++ "file format": "raw",
++ "virtual size": 374784,
++ "disk size": 376832,
++ },
++ },
++ "graphics": {
++ "autoport": "yes",
++ "keymap": "None",
++ "listen": "0.0.0.0",
++ "port": "5900",
++ "type": "spice",
++ },
++ "nics": {
++ "aa:bb:cc:dd:ee:ff": {
++ "type": "network",
++ "mac": "aa:bb:cc:dd:ee:ff",
++ "source": {"network": "default"},
++ "model": "virtio",
++ "address": {
++ "type": "pci",
++ "domain": "0x0000",
++ "bus": "0x00",
++ "slot": "0x03",
++ "function": "0x0",
++ },
++ }
++ },
++ "uuid": "yyyyyy",
++ "loader": {"path": "None"},
++ "on_crash": "destroy",
++ "on_reboot": "restart",
++ "on_poweroff": "destroy",
++ "maxMem": 1048576,
++ "mem": 1048576,
++ "state": "running",
++ },
++ "uyuni-proxy": {
++ "cpu": 2,
++ "cputime": 0,
++ "disks": {
++ "vda": {
++ "file": "default/uyuni-proxy-main-disk",
++ "type": "disk",
++ "file format": "qcow2",
++ "virtual size": 214748364800,
++ "disk size": 4491255808,
++ "backing file": {
++ "file": "/var/lib/libvirt/images/leapmicro55o",
++ "file format": "qcow2",
++ },
++ }
++ },
++ "graphics": {
++ "autoport": "yes",
++ "keymap": "None",
++ "listen": "0.0.0.0",
++ "port": "None",
++ "type": "spice",
++ },
++ "nics": {
++ "aa:bb:cc:dd:ee:aa": {
++ "type": "network",
++ "mac": "aa:bb:cc:dd:ee:aa",
++ "source": {"network": "default"},
++ "model": "virtio",
++ "address": {
++ "type": "pci",
++ "domain": "0x0000",
++ "bus": "0x00",
++ "slot": "0x03",
++ "function": "0x0",
++ },
++ }
++ },
++ "uuid": "xxxxx",
++ "loader": {"path": "None"},
++ "on_crash": "destroy",
++ "on_reboot": "restart",
++ "on_poweroff": "destroy",
++ "maxMem": 2097152,
++ "mem": 2097152,
++ "state": "shutdown",
++ },
++ },
++ }
++ },
++ "outputter": "virt_query",
++ "_stamp": "2025-02-21T11:28:04.406561",
++ },
++ }
++
++
++def test_default_output(data):
++ ret = virt_query.output(data)
++ expected = """mysystem
++ vm1
++ CPU: 2
++ Memory: 1048576
++ State: running
++ Disk - vda:
++ Size: 1831731200
++ File: default/vm1-main-disk
++ File Format: qcow2
++ Disk - hdd:
++ Size: 376832
++ File: default/vm1-cloudinit-disk
++ File Format: raw
++ NIC - aa:bb:cc:dd:ee:ff:
++ Source: default
++ Type: network
++ uyuni-proxy
++ CPU: 2
++ Memory: 2097152
++ State: shutdown
++ Disk - vda:
++ Size: 4491255808
++ File: default/uyuni-proxy-main-disk
++ File Format: qcow2
++ NIC - aa:bb:cc:dd:ee:aa:
++ Source: default
++ Type: network
++"""
++ assert expected == ret
+--
+2.48.1
+
diff --git a/salt.changes b/salt.changes
index 7d4bed7..0f8e8d5 100644
--- a/salt.changes
+++ b/salt.changes
@@ -1,3 +1,41 @@
+-------------------------------------------------------------------
+Mon Feb 24 16:17:55 UTC 2025 - Pablo Suárez Hernández
+
+- Fix issue of using update-alternatives with alts
+
+-------------------------------------------------------------------
+Fri Feb 21 12:46:01 UTC 2025 - Pablo Suárez Hernández
+
+- Fix virt_query outputter and add support for block devices
+- Make _auth calls visible with master stats
+- Repair mount.fstab_present always returning pending changes
+- Set virtual grain in Podman systemd container
+- Fix crash due wrong client reference on `SaltMakoTemplateLookup`
+- Enhace batch async and fix some detected issues
+
+- Added:
+ * repair-virt_query-outputter-655.patch
+ * make-_auth-calls-visible-with-master-stats-696.patch
+ * repair-fstab_present-test-mode-702.patch
+ * set-virtual-grain-in-podman-systemd-container-703.patch
+ * fixed-file-client-private-attribute-reference-on-sal.patch
+ * backport-batch-async-fixes-and-improvements-701.patch
+
+-------------------------------------------------------------------
+Wed Feb 19 16:06:43 UTC 2025 - Pablo Suárez Hernández
+
+- Enhacement of Salt packaging
+ * Use update-alternatives for all salt scripts
+ * Use flexible dependencies for the subpackages
+ * Make salt-minion to require flavored zypp-plugin
+ * Make zyppnotify to use update-alternatives
+ * Drop unused yumnotify plugin
+ * Add dependency to python3-dnf-plugins-core for RHEL based
+- Fix tests failures after "repo.saltproject.io" deprecation
+
+- Added:
+ * fix-tests-failures-after-repo.saltproject.io-depreca.patch
+
-------------------------------------------------------------------
Wed Jan 29 10:34:28 UTC 2025 - Pablo Suárez Hernández
diff --git a/salt.spec b/salt.spec
index 507d275..eff1422 100644
--- a/salt.spec
+++ b/salt.spec
@@ -34,7 +34,7 @@
%define psuffix %{nil}
%endif
-%if 0%{?suse_version} > 1210 || 0%{?rhel} >= 7 || 0%{?fedora} >=28
+%if 0%{?suse_version} > 1210 || 0%{?rhel} >= 7 || 0%{?fedora} >= 28
%bcond_without systemd
%else
%bcond_with systemd
@@ -58,6 +58,7 @@
%{?sle15allpythons}
%define skip_python2 1
%if 0%{?rhel} == 8 || (0%{?suse_version} == 1500 && 0%{?sle_version} < 150400)
+%define singlespec_compat 1
%define __python3_bin_suffix 3.6
%if 0%{?rhel} == 8
%define __python3 /usr/libexec/platform-python
@@ -77,7 +78,6 @@ args = args:gsub("$python_sitelib", "python3_sitelib")\
args = args:gsub("$python", python_bin)\
print(rpm.expand(args .. "\\n"))\
}
-%define _nosinglespec 1
%endif
Name: salt%{psuffix}
Version: 3006.0
@@ -509,6 +509,21 @@ Patch151: enhance-find_json-garbage-filtering-bsc-1231605-688.patch
Patch152: update-for-deprecation-of-hex-in-pygit2-1.15.0-and-a.patch
# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/67186
Patch153: fix-failed-to-stat-root-.gitconfig-issue-on-gitfs-bs.patch
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/commit/58f448405b7f46505b2047ecda72abb42b6df9d1
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/commit/79d4ff772a162b5b8e602e3437c13b90a25bc190
+Patch154: fix-tests-failures-after-repo.saltproject.io-depreca.patch
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/60269
+Patch155: backport-batch-async-fixes-and-improvements-701.patch
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/commit/560ab52ccf94c7974d5a418dfbba7409e0493066
+Patch156: fixed-file-client-private-attribute-reference-on-sal.patch
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/67734
+Patch157: set-virtual-grain-in-podman-systemd-container-703.patch
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/67066
+Patch158: repair-fstab_present-test-mode-702.patch
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/67746
+Patch159: make-_auth-calls-visible-with-master-stats-696.patch
+# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/65843
+Patch160: repair-virt_query-outputter-655.patch
### IMPORTANT: The line below is used as a snippet marker. Do not touch it.
### SALT PATCHES LIST END
@@ -576,10 +591,6 @@ BuildRequires: bash
BuildRequires: zsh
%endif
-%if 0%{?rhel} || 0%{?fedora}
-BuildRequires: yum
-%endif
-
%define python_subpackage_only 1
%python_subpackages
@@ -593,7 +604,7 @@ servers, handle them quickly and through a simple and manageable interface.
%if "%{flavor}" != "testsuite"
-%if 0%{?_nosinglespec}
+%if 0%{?singlespec_compat}
%package -n python3-salt
%else
%package -n python-salt
@@ -650,7 +661,7 @@ BuildRequires: %{python_module sphinx}
%if 0%{?rhel} == 8
Requires: platform-python
%else
-%if 0%{?_nosinglespec}
+%if 0%{?singlespec_compat}
Requires: %{python_module base}
%else
Requires: python-base
@@ -674,16 +685,13 @@ Requires: python3-m2crypto
Requires: python3-markupsafe
Requires: python3-msgpack > 0.3
Requires: python3-zmq >= 2.2.0
-Requires: yum
-%if 0%{?rhel} == 8 || 0%{?fedora} >= 30
+%if 0%{?rhel} >= 8 || 0%{?fedora} >= 30
Requires: dnf
-%endif
-%if 0%{?rhel} == 6
-Requires: yum-plugin-security
+Requires: python3-dnf-plugins-core
%endif
%else # SUSE
-%if 0%{?_nosinglespec}
+%if 0%{?singlespec_compat}
Requires: %{python_module Jinja2}
Requires: %{python_module MarkupSafe}
Requires: %{python_module msgpack-python > 0.3}
@@ -705,7 +713,7 @@ Requires: python-pycrypto >= 2.6.1
Requires: python-pyzmq >= 2.2.0
%endif
%endif # end of RHEL / SUSE specific section
-%if 0%{?_nosinglespec}
+%if 0%{?singlespec_compat}
Recommends: %{python_module jmespath}
Requires: %{python_module PyYAML}
Requires: %{python_module psutil}
@@ -744,17 +752,20 @@ Suggests: python-gnupg
#
%if 0%{?suse_version}
# python-xml is part of python-base in all rhel versions
-%if 0%{?_nosinglespec}
+%if 0%{?singlespec_compat}
Requires: %{python_module xml}
+Requires: %{python_module zypp-plugin}
Suggests: %{python_module Mako}
Recommends: %{python_module netaddr}
Recommends: %{python_module pyinotify}
%else
Requires: python-xml
+Requires: python-zypp-plugin
Suggests: python-Mako
Recommends: python-netaddr
Recommends: python-pyinotify
%endif
+Requires(pre): libzypp(plugin:system) >= 0
%endif
# Required by Salt modules
@@ -764,7 +775,7 @@ Requires: file
Recommends: man
Recommends: python3-passlib
-%if 0%{?_nosinglespec}
+%if 0%{?singlespec_compat}
Provides: bundled(%{python_module tornado}) = 4.5.3
%else
Provides: bundled(python-tornado) = 4.5.3
@@ -772,7 +783,7 @@ Provides: bundled(python-tornado) = 4.5.3
Provides: %{name}-call = %{version}-%{release}
-%if 0%{?_nosinglespec}
+%if 0%{?singlespec_compat}
%description -n python3-salt
%else
%description -n python-salt
@@ -785,7 +796,11 @@ Group: System/Management
Requires: %{name} = %{version}-%{release}
Requires: %{name}-master = %{version}-%{release}
%if 0%{?suse_version}
+%if 0%{?sle_version} >= 150400
+Requires: %{python_module CherryPy >= 3.2.2 if %python-salt}
+%else
Requires: python3-CherryPy >= 3.2.2
+%endif
%else
Requires: python3-cherrypy >= 3.2.2
%endif
@@ -798,11 +813,19 @@ Summary: Generic cloud provisioning tool for Saltstack
Group: System/Management
Requires: %{name} = %{version}-%{release}
Requires: %{name}-master = %{version}-%{release}
-Requires: python3-apache-libcloud
%if 0%{?suse_version}
+%if 0%{?sle_version} >= 150400
+Requires: %{python_module apache-libcloud if %python-salt}
+Recommends: %{python_module botocore if %python-salt}
+Recommends: %{python_module netaddr if %python-salt}
+%else
+Requires: python3-apache-libcloud
Recommends: python3-botocore
Recommends: python3-netaddr
%endif
+%else
+Requires: python3-apache-libcloud
+%endif
%description cloud
public cloud VM management system
@@ -824,8 +847,12 @@ Summary: The management component of Saltstack with zmq protocol supporte
Group: System/Management
Requires: %{name} = %{version}-%{release}
%if 0%{?suse_version}
+%if 0%{?sle_version} >= 150400
+Recommends: %{python_module pygit2 >= 0.20.3 if %python-salt}
+%else
Recommends: python3-pygit2 >= 0.20.3
%endif
+%endif
%ifarch %{ix86} x86_64
%if 0%{?suse_version}
%if 0%{?suse_version} > 1110
@@ -852,10 +879,6 @@ Requires: %{name} = %{version}-%{release}
%if 0%{?suse_version} > 1500 || 0%{?sle_version} > 150000
Requires: (%{name}-transactional-update = %{version}-%{release} if read-only-root-fs)
%endif
-%if 0%{?suse_version}
-Requires: python3-zypp-plugin
-Requires(pre): libzypp(plugin:system) >= 0
-%endif
%if %{with systemd}
%{?systemd_requires}
@@ -980,8 +1003,8 @@ list of active executors. This package add the configuration file.
%if "%{flavor}" == "testsuite"
-%if 0%{?_nosinglespec}
-%package -n %{python_module salt-testsuite}
+%if 0%{?singlespec_compat}
+%package -n python3-salt-testsuite
%else
%package -n python-salt-testsuite
%endif
@@ -995,7 +1018,7 @@ BuildRequires: %{python_module base}
BuildRequires: %{python_module setuptools}
Requires: salt = %{version}
-%if 0%{?_nosinglespec}
+%if 0%{?singlespec_compat}
Recommends: %{python_module CherryPy}
Requires: %{python_module Genshi}
Requires: %{python_module Mako}
@@ -1042,7 +1065,7 @@ Requires: git
Obsoletes: %{name}-tests
-%if 0%{?_nosinglespec}
+%if 0%{?singlespec_compat}
%description -n python3-salt-testsuite
%else
%description -n python-salt-testsuite
@@ -1161,11 +1184,9 @@ cp -a conf %{buildroot}%{$python_sitelib}/salt-testsuite/
%if 0%{?suse_version}
install -Dd -m 0750 %{buildroot}%{_prefix}/lib/zypp/plugins/commit
%{__install} scripts/suse/zypper/plugins/commit/zyppnotify %{buildroot}%{_prefix}/lib/zypp/plugins/commit/zyppnotify
-sed -i '1s=^#!/usr/bin/\(python\|env python\)[0-9.]*=#!/usr/bin/python3=' %{buildroot}%{_prefix}/lib/zypp/plugins/commit/zyppnotify
%endif
-# Install Yum plugins only on RH machines
-%if 0%{?fedora} || 0%{?rhel}
+# Install DNF plugin only on RH machines
%if 0%{?fedora} >= 22 || 0%{?rhel} >= 8
install -Dd %{buildroot}%{python3_sitelib}/dnf-plugins
install -Dd %{buildroot}%{python3_sitelib}/dnf-plugins/__pycache__
@@ -1174,14 +1195,6 @@ install -Dd %{buildroot}%{_sysconfdir}/dnf/plugins
%{__install} scripts/suse/dnf/plugins/dnfnotify.conf %{buildroot}%{_sysconfdir}/dnf/plugins
%{__python3} -m compileall -d %{python3_sitelib}/dnf-plugins %{buildroot}%{python3_sitelib}/dnf-plugins/dnfnotify.py
%{__python3} -O -m compileall -d %{python3_sitelib}/dnf-plugins %{buildroot}%{python3_sitelib}/dnf-plugins/dnfnotify.py
-%else
-install -Dd %{buildroot}%{_prefix}/share/yum-plugins
-install -Dd %{buildroot}%{_sysconfdir}/yum/pluginconf.d
-%{__install} scripts/suse/yum/plugins/yumnotify.py %{buildroot}%{_prefix}/share/yum-plugins
-%{__install} scripts/suse/yum/plugins/yumnotify.conf %{buildroot}%{_sysconfdir}/yum/pluginconf.d
-%{__python} -m compileall -d %{_prefix}/share/yum-plugins %{buildroot}%{_prefix}/share/yum-plugins/yumnotify.py
-%{__python} -O -m compileall -d %{_prefix}/share/yum-plugins %{buildroot}%{_prefix}/share/yum-plugins/yumnotify.py
-%endif
%endif
## install init and systemd scripts
@@ -1252,6 +1265,17 @@ install -Dpm 0640 conf/suse/standalone-formulas-configuration.conf %{buildroot}%
%if 0%{?_alternatives}
%python_clone -a %{buildroot}%{_bindir}/salt-call
+%python_clone -a %{buildroot}%{_bindir}/salt-support
+%python_clone -a %{buildroot}%{_bindir}/spm
+install -Dd -m 0750 %{buildroot}%{_exec_prefix}/libexec/salt
+for SALT_SCRIPT in salt salt-api salt-cloud salt-cp salt-key salt-master salt-minion salt-proxy salt-run salt-ssh salt-syndic; do
+ mv "%{buildroot}%{_bindir}/${SALT_SCRIPT}" "%{buildroot}%{_exec_prefix}/libexec/salt/"
+%python_clone -a %{buildroot}%{_exec_prefix}/libexec/salt/${SALT_SCRIPT}
+ ln -s "%{_exec_prefix}/libexec/salt/${SALT_SCRIPT}" "%{buildroot}%{_bindir}/${SALT_SCRIPT}"
+done
+mv "%{buildroot}%{_prefix}/lib/zypp/plugins/commit/zyppnotify" "%{buildroot}%{_exec_prefix}/libexec/salt/"
+%python_clone -a %{buildroot}%{_exec_prefix}/libexec/salt/zyppnotify
+ln -s "%{_exec_prefix}/libexec/salt/zyppnotify" "%{buildroot}%{_prefix}/lib/zypp/plugins/commit/zyppnotify"
%endif
%endif
@@ -1274,10 +1298,6 @@ getent passwd salt >/dev/null || %{_sbindir}/useradd -r -g salt -d $S_HOME -s /b
if [[ -d "$S_PHOME/.ssh" ]]; then
mv $S_PHOME/.ssh $S_HOME
fi
-%if 0%{?_alternatives}
-[ -h %{_bindir}/salt-call ] || rm -f %{_bindir}/salt-call
-%python_libalternatives_reset_alternative salt-call
-%endif
%post
%if %{with systemd}
@@ -1486,17 +1506,49 @@ fi
%if 0%{?_alternatives}
%pre -n python-salt
-[ -h %{_bindir}/salt-call ] || rm -f %{_bindir}/salt-call
-%python_libalternatives_reset_alternative salt-call
+for SALT_SCRIPT in salt-call salt-support spm; do
+ [ -h "%{_bindir}/${SALT_SCRIPT}" ] || rm -f "%{_bindir}/${SALT_SCRIPT}"
+ if [ "$1" -gt 0 ] && [ -f /usr/sbin/update-alternatives ]; then
+ update-alternatives --quiet --remove "${SALT_SCRIPT}" "%{_bindir}/${SALT_SCRIPT}-%{python_bin_suffix}"
+ fi
+done
+for SALT_SCRIPT in salt salt-api salt-cloud salt-cp salt-key salt-master salt-minion salt-proxy salt-run salt-ssh salt-syndic zyppnotify; do
+ [ -h "%{_exec_prefix}/libexec/salt/${SALT_SCRIPT}" ] || rm -f "%{_exec_prefix}/libexec/salt/${SALT_SCRIPT}"
+ if [ "$1" -gt 0 ] && [ -f /usr/sbin/update-alternatives ]; then
+ update-alternatives --quiet --remove "${SALT_SCRIPT}" "%{_exec_prefix}/libexec/salt/${SALT_SCRIPT}-%{python_bin_suffix}"
+ fi
+done
+%if ! %{with libalternatives}
%post -n python-salt
-%python_install_alternative salt-call
+if [ -f /usr/sbin/update-alternatives ]; then
+ for SALT_SCRIPT in salt-call salt-support spm; do
+ update-alternatives --quiet --install "%{_bindir}/${SALT_SCRIPT}" "${SALT_SCRIPT}" \
+ "%{_bindir}/${SALT_SCRIPT}-%{python_bin_suffix}" %{python_version_nodots}
+ done
+ for SALT_SCRIPT in salt salt-api salt-cloud salt-cp salt-key salt-master salt-minion salt-proxy salt-run salt-ssh salt-syndic zyppnotify; do
+ update-alternatives --quiet --install "%{_exec_prefix}/libexec/salt/${SALT_SCRIPT}" "${SALT_SCRIPT}" \
+ "%{_exec_prefix}/libexec/salt/${SALT_SCRIPT}-%{python_bin_suffix}" %{python_version_nodots}
+ done
+fi
%postun -n python-salt
-%python_uninstall_alternative salt-call
+if [ -f /usr/sbin/update-alternatives ]; then
+ for SALT_SCRIPT in salt-call salt-support spm; do
+ if [ ! -e "%{_bindir}/${SALT_SCRIPT}-%{python_bin_suffix}" ]; then
+ update-alternatives --quiet --remove "${SALT_SCRIPT}" "%{_bindir}/${SALT_SCRIPT}-%{python_bin_suffix}"
+ fi
+ done
+ for SALT_SCRIPT in salt salt-api salt-cloud salt-cp salt-key salt-master salt-minion salt-proxy salt-run salt-ssh salt-syndic zyppnotify; do
+ if [ ! -e "%{_exec_prefix}/libexec/salt/${SALT_SCRIPT}-%{python_bin_suffix}" ]; then
+ update-alternatives --quiet --remove "${SALT_SCRIPT}" "%{_exec_prefix}/libexec/salt/${SALT_SCRIPT}-%{python_bin_suffix}"
+ fi
+ done
+fi
+%endif
%endif
-%if 0%{?_nosinglespec}
+%if 0%{?singlespec_compat}
%posttrans -n %{python_module salt}
%else
%posttrans -n python-salt
@@ -1559,16 +1611,11 @@ rm -f %{_localstatedir}/cache/salt/minion/thin/version
%{_prefix}/lib/zypp/plugins/commit/zyppnotify
%endif
-# Install Yum plugins only on RH machines
-%if 0%{?fedora} || 0%{?rhel}
+# Install DNF plugin only on RH machines
%if 0%{?fedora} >= 22 || 0%{?rhel} >= 8
%{python3_sitelib}/dnf-plugins/dnfnotify.py
%{python3_sitelib}/dnf-plugins/__pycache__/dnfnotify.*
%{_sysconfdir}/dnf/plugins/dnfnotify.conf
-%else
-%{_prefix}/share/yum-plugins/yumnotify.*
-%{_sysconfdir}/yum/pluginconf.d/yumnotify.conf
-%endif
%endif
%if %{with systemd}
@@ -1651,7 +1698,24 @@ rm -f %{_localstatedir}/cache/salt/minion/thin/version
%defattr(-,root,root,-)
%if 0%{?_alternatives}
%python_alternative %{_bindir}/salt-call
+%python_alternative %{_bindir}/salt-support
+%python_alternative %{_bindir}/spm
+%dir %{_exec_prefix}/libexec
+%dir %attr(0755, root, root) %{_exec_prefix}/libexec/salt
+%python_alternative %{_exec_prefix}/libexec/salt/salt
+%python_alternative %{_exec_prefix}/libexec/salt/salt-api
+%python_alternative %{_exec_prefix}/libexec/salt/salt-cloud
+%python_alternative %{_exec_prefix}/libexec/salt/salt-cp
+%python_alternative %{_exec_prefix}/libexec/salt/salt-key
+%python_alternative %{_exec_prefix}/libexec/salt/salt-master
+%python_alternative %{_exec_prefix}/libexec/salt/salt-minion
+%python_alternative %{_exec_prefix}/libexec/salt/salt-proxy
+%python_alternative %{_exec_prefix}/libexec/salt/salt-run
+%python_alternative %{_exec_prefix}/libexec/salt/salt-ssh
+%python_alternative %{_exec_prefix}/libexec/salt/salt-syndic
+%python_alternative %{_exec_prefix}/libexec/salt/zyppnotify
%endif
+
%dir %{python_sitelib}/salt
%dir %{python_sitelib}/salt-*.egg-info
%{python_sitelib}/salt/*
diff --git a/set-virtual-grain-in-podman-systemd-container-703.patch b/set-virtual-grain-in-podman-systemd-container-703.patch
new file mode 100644
index 0000000..452a89e
--- /dev/null
+++ b/set-virtual-grain-in-podman-systemd-container-703.patch
@@ -0,0 +1,84 @@
+From dde665763bd2f043022f9601dd25d0ca8aa716be Mon Sep 17 00:00:00 2001
+From: Georg
+Date: Fri, 21 Feb 2025 10:24:51 +0000
+Subject: [PATCH] Set virtual grain in Podman systemd container (#703)
+
+Correctly handle the systemd-detect-virt output to identify a Podman
+container running systemd as what it is instead of as a physical machine.
+
+Signed-off-by: Georg Pfuetzenreuter
+(cherry picked from commit cf504a06859fb4a4fe9b8ebdd76380697f1f0c25)
+---
+ changelog/67733.fixed.md | 1 +
+ salt/grains/core.py | 4 ++++
+ tests/pytests/unit/grains/test_core.py | 31 ++++++++++++++++++++++++++
+ 3 files changed, 36 insertions(+)
+ create mode 100644 changelog/67733.fixed.md
+
+diff --git a/changelog/67733.fixed.md b/changelog/67733.fixed.md
+new file mode 100644
+index 0000000000..242f65ec76
+--- /dev/null
++++ b/changelog/67733.fixed.md
+@@ -0,0 +1 @@
++Set correct virtual grain in systemd based Podman containers
+diff --git a/salt/grains/core.py b/salt/grains/core.py
+index 84d5b179dd..ceb142a7b8 100644
+--- a/salt/grains/core.py
++++ b/salt/grains/core.py
+@@ -911,6 +911,10 @@ def _virtual(osdata):
+ grains["virtual"] = "container"
+ grains["virtual_subtype"] = "LXC"
+ break
++ elif "podman" in output:
++ grains["virtual"] = "container"
++ grains["virtual_subtype"] = "Podman"
++ break
+ elif "amazon" in output:
+ grains["virtual"] = "Nitro"
+ grains["virtual_subtype"] = "Amazon EC2"
+diff --git a/tests/pytests/unit/grains/test_core.py b/tests/pytests/unit/grains/test_core.py
+index 3d2beaa2c9..072287248f 100644
+--- a/tests/pytests/unit/grains/test_core.py
++++ b/tests/pytests/unit/grains/test_core.py
+@@ -1752,6 +1752,37 @@ def test_lxc_virtual_with_virt_what():
+ assert ret["virtual_subtype"] == "LXC"
+
+
++@pytest.mark.skip_on_windows
++def test_podman_virtual_with_systemd_detect_virt():
++ """
++ Test if virtual grains are parsed correctly in Podman using systemd-detect-virt.
++ """
++
++ def _which_side_effect(path):
++ if path == "systemd-detect-virt":
++ return "/usr/bin/systemd-detect-virt"
++ return None
++
++ with patch.object(
++ salt.utils.platform, "is_windows", MagicMock(return_value=False)
++ ), patch.object(
++ salt.utils.path,
++ "which",
++ MagicMock(return_value=True, side_effect=_which_side_effect),
++ ), patch.dict(
++ core.__salt__,
++ {
++ "cmd.run_all": MagicMock(
++ return_value={"pid": 78, "retcode": 0, "stderr": "", "stdout": "podman"}
++ )
++ },
++ ):
++ osdata = {"kernel": "test"}
++ ret = core._virtual(osdata)
++ assert ret["virtual"] == "container"
++ assert ret["virtual_subtype"] == "Podman"
++
++
+ @pytest.mark.skip_on_windows
+ def test_container_inside_virtual_machine():
+ """
+--
+2.48.1
+