Merge pull request #64688 from s0undt3ch/hotfix/merge-forward

[master] Merge 3006.x into master
This commit is contained in:
Pedro Algarvio 2023-07-22 21:18:19 +01:00 committed by GitHub
commit f7787f2d93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 541 additions and 335 deletions

4
.github/config.yml vendored
View file

@ -13,7 +13,7 @@ newIssueWelcomeComment: >
- [Community Wiki](https://github.com/saltstack/community/wiki)
- [Salts Contributor Guide](https://docs.saltproject.io/en/master/topics/development/contributing.html)
- [Join our Community Slack](https://join.slack.com/t/saltstackcommunity/shared_invite/zt-3av8jjyf-oBQ2M0vhXOhJpNpRkPWBvg)
- [Join our Community Slack](https://join.slack.com/t/saltstackcommunity/shared_invite/zt-1zlfxffs1-NuEH~G9TzOeuNGdsfZIl3w)
- [IRC on LiberaChat](https://web.libera.chat/#salt)
- [Salt Project YouTube channel](https://www.youtube.com/channel/UCpveTIucFx9ljGelW63-BWg)
- [Salt Project Twitch channel](https://www.twitch.tv/saltprojectoss)
@ -39,7 +39,7 @@ newPRWelcomeComment: >
- [Community Wiki](https://github.com/saltstack/community/wiki)
- [Salts Contributor Guide](https://docs.saltproject.io/en/master/topics/development/contributing.html)
- [Join our Community Slack](https://join.slack.com/t/saltstackcommunity/shared_invite/zt-3av8jjyf-oBQ2M0vhXOhJpNpRkPWBvg)
- [Join our Community Slack](https://join.slack.com/t/saltstackcommunity/shared_invite/zt-1zlfxffs1-NuEH~G9TzOeuNGdsfZIl3w)
- [IRC on LiberaChat](https://web.libera.chat/#salt)
- [Salt Project YouTube channel](https://www.youtube.com/channel/UCpveTIucFx9ljGelW63-BWg)
- [Salt Project Twitch channel](https://www.twitch.tv/saltprojectoss)

View file

@ -8,7 +8,7 @@ in a number of ways:
- Use Salt and open well-written bug reports.
- Join a `working group <https://github.com/saltstack/community>`__.
- Answer questions on `irc <https://web.libera.chat/#salt>`__,
the `community Slack <https://join.slack.com/t/saltstackcommunity/shared_invite/zt-3av8jjyf-oBQ2M0vhXOhJpNpRkPWBvg>`__,
the `community Slack <https://join.slack.com/t/saltstackcommunity/shared_invite/zt-1zlfxffs1-NuEH~G9TzOeuNGdsfZIl3w>`__,
the `salt-users mailing
list <https://groups.google.com/forum/#!forum/salt-users>`__,
`Server Fault <https://serverfault.com/questions/tagged/saltstack>`__,

View file

@ -12,7 +12,7 @@
.. image:: https://img.shields.io/badge/slack-@saltstackcommunity-blue.svg?logo=slack
:alt: Salt Project Slack Community
:target: https://join.slack.com/t/saltstackcommunity/shared_invite/zt-3av8jjyf-oBQ2M0vhXOhJpNpRkPWBvg
:target: https://join.slack.com/t/saltstackcommunity/shared_invite/zt-1zlfxffs1-NuEH~G9TzOeuNGdsfZIl3w
.. image:: https://img.shields.io/twitch/status/saltprojectoss
:alt: Salt Project Twitch Channel
@ -71,7 +71,8 @@ In addition to configuration management Salt can also:
About our sponsors
==================
Salt powers VMware's `vRealize Automation SaltStack Config`_, and can be found
Salt powers VMware's `VMware Aria Automation Config`_
(previously vRealize Automation SaltStack Config / SaltStack Enterprise), and can be found
under the hood of products from Juniper, Cisco, Cloudflare, Nutanix, SUSE, and
Tieto, to name a few.
@ -179,8 +180,8 @@ used by external modules.
A complete list of attributions and dependencies can be found here:
`salt/DEPENDENCIES.md <https://github.com/saltstack/salt/blob/master/DEPENDENCIES.md>`_
.. _Salt Project Community Slack: https://join.slack.com/t/saltstackcommunity/shared_invite/zt-3av8jjyf-oBQ2M0vhXOhJpNpRkPWBvg
.. _vRealize Automation SaltStack Config: https://www.vmware.com/products/vrealize-automation/saltstack-config.html
.. _Salt Project Community Slack: https://join.slack.com/t/saltstackcommunity/shared_invite/zt-1zlfxffs1-NuEH~G9TzOeuNGdsfZIl3w
.. _VMware Aria Automation Config: https://www.vmware.com/products/vrealize-automation/saltstack-config.html
.. _Latest Salt Documentation: https://docs.saltproject.io/en/latest/
.. _Open an issue: https://github.com/saltstack/salt/issues/new/choose
.. _SECURITY.md: https://github.com/saltstack/salt/blob/master/SECURITY.md

View file

@ -11,7 +11,7 @@ it may take a few moments for someone to reply.
**SaltStack Slack** - Alongside IRC is our SaltStack Community Slack for the
SaltStack Working groups. Use the following link to request an invitation.
`<https://join.slack.com/t/saltstackcommunity/shared_invite/zt-3av8jjyf-oBQ2M0vhXOhJpNpRkPWBvg>`_
`<https://join.slack.com/t/saltstackcommunity/shared_invite/zt-1zlfxffs1-NuEH~G9TzOeuNGdsfZIl3w>`_
**Mailing List** - The SaltStack community users mailing list is hosted by
Google groups. Anyone can post to ask questions about SaltStack products and

1
changelog/64622.fixed.md Normal file
View file

@ -0,0 +1 @@
Added support for Chocolatey 2.0.0+

View file

@ -0,0 +1 @@
Bump to `aiohttp==3.8.5` due to https://github.com/advisories/GHSA-45c4-8wx5-qw6w

View file

@ -174,7 +174,7 @@ rst_prolog = """\
.. _`salt-users`: https://groups.google.com/forum/#!forum/salt-users
.. _`salt-announce`: https://groups.google.com/forum/#!forum/salt-announce
.. _`salt-packagers`: https://groups.google.com/forum/#!forum/salt-packagers
.. _`salt-slack`: https://join.slack.com/t/saltstackcommunity/shared_invite/zt-3av8jjyf-oBQ2M0vhXOhJpNpRkPWBvg
.. _`salt-slack`: https://join.slack.com/t/saltstackcommunity/shared_invite/zt-1zlfxffs1-NuEH~G9TzOeuNGdsfZIl3w
.. |windownload| raw:: html
<p>Python3 x86: <a

View file

@ -1197,8 +1197,8 @@ seconds each iteration.
Default: ``False``
If the master rejects the minion's public key, retry instead of exiting.
Rejected keys will be handled the same as waiting on acceptance.
If the master denies or rejects the minion's public key, retry instead of
exiting. These keys will be handled the same as waiting on acceptance.
.. code-block:: yaml

View file

@ -4,7 +4,7 @@
#
# pip-compile --no-emit-index-url --output-file=requirements/static/ci/py3.10/darwin.txt requirements/darwin.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/darwin.in requirements/static/pkg/darwin.in
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -4,7 +4,7 @@
#
# pip-compile --no-emit-index-url --output-file=requirements/static/ci/py3.10/freebsd.txt requirements/base.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in requirements/zeromq.txt
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -6,7 +6,7 @@
#
aiohttp-retry==2.8.3
# via twilio
aiohttp==3.8.4
aiohttp==3.8.5
# via
# aiohttp-retry
# etcd3-py

View file

@ -4,7 +4,7 @@
#
# pip-compile --no-emit-index-url --output-file=requirements/static/ci/py3.10/windows.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/windows.in requirements/static/pkg/windows.in requirements/windows.txt
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -4,7 +4,7 @@
#
# pip-compile --output-file=requirements/static/ci/py3.11/darwin.txt requirements/darwin.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/darwin.in requirements/static/pkg/darwin.in
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -4,7 +4,7 @@
#
# pip-compile --no-emit-index-url --output-file=requirements/static/ci/py3.11/freebsd.txt requirements/base.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in requirements/zeromq.txt
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -6,7 +6,7 @@
#
aiohttp-retry==2.8.3
# via twilio
aiohttp==3.8.4
aiohttp==3.8.5
# via
# aiohttp-retry
# etcd3-py

View file

@ -4,7 +4,7 @@
#
# pip-compile --no-emit-index-url --output-file=requirements/static/ci/py3.11/windows.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/windows.in requirements/static/pkg/windows.in requirements/windows.txt
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -4,7 +4,7 @@
#
# pip-compile --no-emit-index-url --output-file=requirements/static/ci/py3.8/freebsd.txt requirements/base.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in requirements/zeromq.txt
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -6,7 +6,7 @@
#
aiohttp-retry==2.8.3
# via twilio
aiohttp==3.8.4
aiohttp==3.8.5
# via
# aiohttp-retry
# etcd3-py

View file

@ -4,7 +4,7 @@
#
# pip-compile --no-emit-index-url --output-file=requirements/static/ci/py3.8/windows.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/windows.in requirements/static/pkg/windows.in requirements/windows.txt
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -4,7 +4,7 @@
#
# pip-compile --no-emit-index-url --output-file=requirements/static/ci/py3.9/darwin.txt requirements/darwin.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/darwin.in requirements/static/pkg/darwin.in
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -4,7 +4,7 @@
#
# pip-compile --no-emit-index-url --output-file=requirements/static/ci/py3.9/freebsd.txt requirements/base.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/freebsd.in requirements/static/pkg/freebsd.in requirements/zeromq.txt
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -6,7 +6,7 @@
#
aiohttp-retry==2.8.3
# via twilio
aiohttp==3.8.4
aiohttp==3.8.5
# via
# aiohttp-retry
# etcd3-py

View file

@ -4,7 +4,7 @@
#
# pip-compile --no-emit-index-url --output-file=requirements/static/ci/py3.9/windows.txt requirements/pytest.txt requirements/static/ci/common.in requirements/static/ci/windows.in requirements/static/pkg/windows.in requirements/windows.txt
#
aiohttp==3.8.4
aiohttp==3.8.5
# via etcd3-py
aiosignal==1.3.1
# via aiohttp

View file

@ -340,8 +340,6 @@ class LoadAuth:
load["user"] == self.opts.get("user", "root") or load["user"] == "root"
):
for check_key in key:
dgm_user = self.opts.get("user", "root")
dgm_check_key = key[check_key]
if auth_key == key[check_key]:
return True
log.warning(

View file

@ -460,7 +460,11 @@ def list_(
salt '*' chocolatey.list <narrow> all_versions=True
"""
choc_path = _find_chocolatey()
cmd = [choc_path, "list"]
# https://docs.chocolatey.org/en-us/guides/upgrading-to-chocolatey-v2-v6
if Version(chocolatey_version()) < Version("2.0.0"):
cmd = [choc_path, "list"]
else:
cmd = [choc_path, "search"]
if narrow:
cmd.append(narrow)
if salt.utils.data.is_true(all_versions):
@ -518,7 +522,11 @@ def list_webpi():
salt '*' chocolatey.list_webpi
"""
choc_path = _find_chocolatey()
cmd = [choc_path, "list", "--source", "webpi"]
# https://docs.chocolatey.org/en-us/guides/upgrading-to-chocolatey-v2-v6
if Version(chocolatey_version()) < Version("2.0.0"):
cmd = [choc_path, "list", "--source", "webpi"]
else:
cmd = [choc_path, "search", "--source", "webpi"]
result = __salt__["cmd.run_all"](cmd, python_shell=False)
if result["retcode"] != 0:
@ -543,7 +551,11 @@ def list_windowsfeatures():
salt '*' chocolatey.list_windowsfeatures
"""
choc_path = _find_chocolatey()
cmd = [choc_path, "list", "--source", "windowsfeatures"]
# https://docs.chocolatey.org/en-us/guides/upgrading-to-chocolatey-v2-v6
if Version(chocolatey_version()) < Version("2.0.0"):
cmd = [choc_path, "list", "--source", "windowsfeatures"]
else:
cmd = [choc_path, "search", "--source", "windowsfeatures"]
result = __salt__["cmd.run_all"](cmd, python_shell=False)
if result["retcode"] != 0:

View file

@ -30,8 +30,8 @@ salt/modules/(aix_group|groupadd|mac_group|pw_group|solaris_group|win_groupadd)\
salt/modules/(debian_service|freebsdservice|gentoo_service|launchctl_service|mac_service|netbsdservice|openbsdrcctl_service|openbsdservice|rh_service|runit|linux_service|smf_service|systemd_service|upstart_service|win_service)\.py:
- pytests.unit.states.test_service
- integration.modules.test_service
- integration.states.test_service
- pytests.functional.modules.test_service
- pytests.functional.states.test_service
salt/modules/ansiblegate.py:

View file

@ -1,183 +0,0 @@
import pytest
import salt.utils.path
import salt.utils.platform
import salt.utils.systemd
from tests.support.case import ModuleCase
@pytest.mark.destructive_test
@pytest.mark.windows_whitelisted
class ServiceModuleTest(ModuleCase):
"""
Module testing the service module
"""
def setUp(self):
self.service_name = "cron"
cmd_name = "crontab"
os_family = self.run_function("grains.get", ["os_family"])
os_release = self.run_function("grains.get", ["osrelease"])
if os_family == "RedHat":
if os_release[0] == "7":
self.skipTest(
"Disabled on CentOS 7 until we can fix SSH connection issues."
)
self.service_name = "crond"
elif os_family == "Arch":
self.service_name = "sshd"
cmd_name = "systemctl"
elif os_family == "NILinuxRT":
self.service_name = "syslog"
cmd_name = "syslog-ng"
elif os_family == "MacOS":
self.service_name = "com.apple.AirPlayXPCHelper"
elif salt.utils.platform.is_windows():
self.service_name = "Spooler"
self.pre_srv_status = self.run_function("service.status", [self.service_name])
self.pre_srv_enabled = (
True
if self.service_name in self.run_function("service.get_enabled")
else False
)
if (
salt.utils.path.which(cmd_name) is None
and not salt.utils.platform.is_windows()
):
self.skipTest("{} is not installed".format(cmd_name))
def tearDown(self):
post_srv_status = self.run_function("service.status", [self.service_name])
post_srv_enabled = (
True
if self.service_name in self.run_function("service.get_enabled")
else False
)
if post_srv_status != self.pre_srv_status:
if self.pre_srv_status:
self.run_function("service.enable", [self.service_name])
else:
self.run_function("service.disable", [self.service_name])
if post_srv_enabled != self.pre_srv_enabled:
if self.pre_srv_enabled:
self.run_function("service.enable", [self.service_name])
else:
self.run_function("service.disable", [self.service_name])
del self.service_name
@pytest.mark.flaky(max_runs=4)
@pytest.mark.slow_test
def test_service_status_running(self):
"""
test service.status execution module
when service is running
"""
self.run_function("service.start", [self.service_name])
check_service = self.run_function("service.status", [self.service_name])
self.assertTrue(check_service)
@pytest.mark.slow_test
def test_service_status_dead(self):
"""
test service.status execution module
when service is dead
"""
self.run_function("service.stop", [self.service_name])
check_service = self.run_function("service.status", [self.service_name])
self.assertFalse(check_service)
@pytest.mark.slow_test
def test_service_restart(self):
"""
test service.restart
"""
self.assertTrue(self.run_function("service.restart", [self.service_name]))
@pytest.mark.slow_test
def test_service_enable(self):
"""
test service.get_enabled and service.enable module
"""
# disable service before test
self.assertTrue(self.run_function("service.disable", [self.service_name]))
self.assertTrue(self.run_function("service.enable", [self.service_name]))
self.assertIn(self.service_name, self.run_function("service.get_enabled"))
@pytest.mark.slow_test
def test_service_disable(self):
"""
test service.get_disabled and service.disable module
"""
# enable service before test
self.assertTrue(self.run_function("service.enable", [self.service_name]))
self.assertTrue(self.run_function("service.disable", [self.service_name]))
if salt.utils.platform.is_darwin():
self.assertTrue(self.run_function("service.disabled", [self.service_name]))
else:
self.assertIn(self.service_name, self.run_function("service.get_disabled"))
@pytest.mark.slow_test
def test_service_disable_doesnot_exist(self):
"""
test service.get_disabled and service.disable module
when service name does not exist
"""
# enable service before test
srv_name = "doesnotexist"
enable = self.run_function("service.enable", [srv_name])
systemd = salt.utils.systemd.booted()
# check service was not enabled
try:
self.assertFalse(enable)
except AssertionError:
self.assertIn("ERROR", enable)
# check service was not disabled
if (
tuple(
self.run_function("grains.item", ["osrelease_info"])["osrelease_info"]
)
== (14, 0o4)
and not systemd
):
# currently upstart does not have a mechanism to report if disabling a service fails if does not exist
self.assertTrue(self.run_function("service.disable", [srv_name]))
elif (
self.run_function("grains.item", ["os"])["os"] == "Debian"
and self.run_function("grains.item", ["osmajorrelease"])["osmajorrelease"]
< 9
and systemd
):
# currently disabling a service via systemd that does not exist
# on Debian 8 results in a True return code
self.assertTrue(self.run_function("service.disable", [srv_name]))
else:
try:
disable = self.run_function("service.disable", [srv_name])
self.assertFalse(disable)
except AssertionError:
self.assertTrue("error" in disable.lower())
if salt.utils.platform.is_darwin():
self.assertEqual(
self.run_function("service.disabled", [srv_name]),
"ERROR: Service not found: {}".format(srv_name),
)
else:
self.assertNotIn(srv_name, self.run_function("service.get_disabled"))
@pytest.mark.skip_unless_on_windows
@pytest.mark.slow_test
def test_service_get_service_name(self):
"""
test service.get_service_name
"""
ret = self.run_function("service.get_service_name")
self.assertIn(self.service_name, ret.values())

View file

@ -1,119 +0,0 @@
"""
Tests for the service state
"""
import re
import pytest
import salt.utils.path
import salt.utils.platform
from tests.support.case import ModuleCase
from tests.support.mixins import SaltReturnAssertsMixin
INIT_DELAY = 5
@pytest.mark.windows_whitelisted
@pytest.mark.destructive_test
class ServiceTest(ModuleCase, SaltReturnAssertsMixin):
"""
Validate the service state
"""
def setUp(self):
self.service_name = "cron"
cmd_name = "crontab"
os_family = self.run_function("grains.get", ["os_family"])
os_release = self.run_function("grains.get", ["osrelease"])
is_systemd = self.run_function("grains.get", ["systemd"])
self.stopped = False
self.running = True
if os_family == "RedHat":
self.service_name = "crond"
elif os_family == "Arch":
self.service_name = "sshd"
cmd_name = "systemctl"
elif os_family == "MacOS":
self.service_name = "com.apple.AirPlayXPCHelper"
elif os_family == "Windows":
self.service_name = "Spooler"
self.pre_srv_enabled = (
True
if self.service_name in self.run_function("service.get_enabled")
else False
)
self.post_srv_disable = False
if not self.pre_srv_enabled:
self.run_function("service.enable", name=self.service_name)
self.post_srv_disable = True
if os_family != "Windows" and salt.utils.path.which(cmd_name) is None:
self.skipTest("{} is not installed".format(cmd_name))
if is_systemd and self.run_function("service.offline"):
self.skipTest("systemd is OFFLINE")
def tearDown(self):
if self.post_srv_disable:
self.run_function("service.disable", name=self.service_name)
def check_service_status(self, exp_return):
"""
helper method to check status of service
"""
check_status = self.run_function("service.status", name=self.service_name)
try:
if not re.match(exp_return, check_status):
self.fail("status of service is not returning correctly")
except TypeError:
if check_status is not exp_return:
self.fail("status of service is not returning correctly")
@pytest.mark.slow_test
def test_service_running(self):
"""
test service.running state module
"""
if self.run_function("service.status", name=self.service_name):
stop_service = self.run_function("service.stop", name=self.service_name)
self.assertTrue(stop_service)
self.check_service_status(self.stopped)
if salt.utils.platform.is_darwin():
# make sure the service is enabled on macosx
enable = self.run_function("service.enable", name=self.service_name)
start_service = self.run_state("service.running", name=self.service_name)
self.assertTrue(start_service)
self.check_service_status(self.running)
@pytest.mark.slow_test
def test_service_dead(self):
"""
test service.dead state module
"""
start_service = self.run_state("service.running", name=self.service_name)
self.assertSaltTrueReturn(start_service)
self.check_service_status(self.running)
ret = self.run_state("service.dead", name=self.service_name)
self.assertSaltTrueReturn(ret)
self.check_service_status(self.stopped)
@pytest.mark.slow_test
def test_service_dead_init_delay(self):
"""
test service.dead state module with init_delay arg
"""
start_service = self.run_state("service.running", name=self.service_name)
self.assertSaltTrueReturn(start_service)
self.check_service_status(self.running)
ret = self.run_state(
"service.dead", name=self.service_name, init_delay=INIT_DELAY
)
self.assertSaltTrueReturn(ret)
self.check_service_status(self.stopped)

View file

@ -0,0 +1,157 @@
import os
import pytest
import salt.utils.path
import salt.utils.platform
import salt.utils.systemd
from salt.exceptions import CommandExecutionError
pytestmark = [
pytest.mark.windows_whitelisted,
pytest.mark.destructive_test,
pytest.mark.slow_test,
]
@pytest.fixture
def service_name(grains, modules):
# For local testing purposes
env_name = os.environ.get("SALT_FUNCTIONAL_TEST_SERVICE_NAME")
if env_name is not None:
return env_name
service_name = "cron"
cmd_name = "crontab"
os_family = grains.get("os_family")
is_systemd = grains.get("systemd")
if os_family == "RedHat":
service_name = "crond"
elif os_family == "Arch":
service_name = "sshd"
cmd_name = "systemctl"
elif os_family == "MacOS":
service_name = "com.apple.AirPlayXPCHelper"
elif os_family == "Windows":
service_name = "Spooler"
if os_family != "Windows" and salt.utils.path.which(cmd_name) is None:
pytest.skip(f"{cmd_name} is not installed")
if is_systemd and modules.service.offline():
pytest.skip("systemd is OFFLINE")
return service_name
@pytest.fixture(autouse=True)
def setup_service(service_name, modules):
pre_srv_status = modules.service.status(service_name)
pre_srv_enabled = service_name in modules.service.get_enabled()
try:
yield pre_srv_status
finally:
post_srv_status = modules.service.status(service_name)
post_srv_enabled = service_name in modules.service.get_enabled()
if post_srv_status != pre_srv_status:
if pre_srv_status:
modules.service.start(service_name)
else:
modules.service.stop(service_name)
if post_srv_enabled != pre_srv_enabled:
if pre_srv_enabled:
modules.service.enable(service_name)
else:
modules.service.disable(service_name)
def test_service_status_running(modules, service_name):
"""
test service.status execution module
when service is running
"""
modules.service.start(service_name)
check_service = modules.service.status(service_name)
assert check_service
def test_service_status_dead(modules, service_name):
"""
test service.status execution module
when service is dead
"""
modules.service.stop(service_name)
check_service = modules.service.status(service_name)
assert not check_service
def test_service_restart(modules, service_name):
"""
test service.restart
"""
assert modules.service.stop(service_name)
def test_service_enable(modules, service_name):
"""
test service.get_enabled and service.enable module
"""
# disable service before test
assert modules.service.disable(service_name)
assert modules.service.enable(service_name)
assert service_name in modules.service.get_enabled()
def test_service_disable(modules, service_name):
"""
test service.get_disabled and service.disable module
"""
# enable service before test
assert modules.service.enable(service_name)
assert modules.service.disable(service_name)
if salt.utils.platform.is_darwin():
assert modules.service.disabled(service_name)
else:
assert service_name in modules.service.get_disabled()
def test_service_disable_doesnot_exist(modules):
"""
test service.get_disabled and service.disable module
when service name does not exist
"""
# enable service before test
srv_name = "doesnotexist"
try:
enable = modules.service.enable(srv_name)
assert not enable
except CommandExecutionError as exc:
assert srv_name in exc.error or "no such file or directory" in exc.error.lower()
try:
disable = modules.service.disable(srv_name)
assert not disable
except CommandExecutionError as exc:
assert srv_name in exc.error or "no such file or directory" in exc.error.lower()
if salt.utils.platform.is_darwin():
with pytest.raises(
CommandExecutionError, match=f"Service not found: {srv_name}"
):
modules.service.disabled(srv_name)
else:
assert srv_name not in modules.service.get_disabled()
@pytest.mark.skip_unless_on_windows
def test_service_get_service_name(modules, service_name):
"""
test service.get_service_name
"""
ret = modules.service.get_service_name()
assert service_name in ret.values()

View file

@ -0,0 +1,132 @@
"""
Tests for the service state
"""
import os
import pytest
import salt.utils.path
import salt.utils.platform
pytestmark = [
pytest.mark.windows_whitelisted,
pytest.mark.destructive_test,
pytest.mark.slow_test,
]
INIT_DELAY = 5
STOPPED = False
RUNNING = True
@pytest.fixture
def service_name(grains, modules):
# For local testing purposes
env_name = os.environ.get("SALT_FUNCTIONAL_TEST_SERVICE_NAME")
if env_name is not None:
return env_name
service_name = "cron"
cmd_name = "crontab"
os_family = grains.get("os_family")
is_systemd = grains.get("systemd")
if os_family == "RedHat":
service_name = "crond"
elif os_family == "Arch":
service_name = "sshd"
cmd_name = "systemctl"
elif os_family == "MacOS":
service_name = "com.apple.AirPlayXPCHelper"
elif os_family == "Windows":
service_name = "Spooler"
if os_family != "Windows" and salt.utils.path.which(cmd_name) is None:
pytest.skip(f"{cmd_name} is not installed")
if is_systemd and modules.service.offline():
pytest.skip("systemd is OFFLINE")
return service_name
@pytest.fixture(autouse=True)
def setup_service(service_name, modules):
pre_srv_status = modules.service.status(service_name)
pre_srv_enabled = service_name in modules.service.get_enabled()
try:
yield pre_srv_status
finally:
post_srv_status = modules.service.status(service_name)
post_srv_enabled = service_name in modules.service.get_enabled()
if post_srv_status != pre_srv_status:
if pre_srv_status:
modules.service.start(service_name)
else:
modules.service.stop(service_name)
if post_srv_enabled != pre_srv_enabled:
if pre_srv_enabled:
modules.service.enable(service_name)
else:
modules.service.disable(service_name)
def check_service_status(exp_return, modules, service_name):
"""
helper method to check status of service
"""
check_status = modules.service.status(service_name)
if check_status is not exp_return:
pytest.fail("status of service is not returning correctly")
@pytest.mark.slow_test
def test_service_running(service_name, modules, states):
"""
test service.running state module
"""
if modules.service.status(service_name):
stop_service = modules.service.stop(service_name)
assert stop_service is True
check_service_status(STOPPED, modules, service_name)
if salt.utils.platform.is_darwin():
# make sure the service is enabled on macosx
enable = modules.service.enable(service_name)
start_service = states.service.running(service_name)
assert start_service.full_return["result"] is True
check_service_status(RUNNING, modules, service_name)
@pytest.mark.slow_test
def test_service_dead(service_name, modules, states):
"""
test service.dead state module
"""
start_service = states.service.running(service_name)
assert start_service.full_return["result"] is True
check_service_status(RUNNING, modules, service_name)
ret = states.service.dead(service_name)
assert ret.full_return["result"] is True
check_service_status(STOPPED, modules, service_name)
@pytest.mark.slow_test
def test_service_dead_init_delay(service_name, modules, states):
"""
test service.dead state module
"""
start_service = states.service.running(service_name)
assert start_service.full_return["result"] is True
check_service_status(RUNNING, modules, service_name)
ret = states.service.dead(service_name, init_delay=INIT_DELAY)
assert ret.full_return["result"] is True
check_service_status(STOPPED, modules, service_name)

View file

@ -262,3 +262,75 @@ def test_add_source(choco_path):
"source_name", "source_location", priority="priority"
)
cmd_run_all_mock.assert_called_with(expected_call, python_shell=False)
def test_list_pre_2_0_0():
mock_version = MagicMock(return_value="1.2.1")
mock_find = MagicMock(return_value=choco_path)
mock_run = MagicMock(return_value={"stdout": "No packages", "retcode": 0})
with patch.object(chocolatey, "chocolatey_version", mock_version), patch.object(
chocolatey, "_find_chocolatey", mock_find
), patch.dict(chocolatey.__salt__, {"cmd.run_all": mock_run}):
chocolatey.list_()
expected_call = [choco_path, "list", "--limit-output"]
mock_run.assert_called_with(expected_call, python_shell=False)
def test_list_post_2_0_0():
mock_version = MagicMock(return_value="2.0.1")
mock_find = MagicMock(return_value=choco_path)
mock_run = MagicMock(return_value={"stdout": "No packages", "retcode": 0})
with patch.object(chocolatey, "chocolatey_version", mock_version), patch.object(
chocolatey, "_find_chocolatey", mock_find
), patch.dict(chocolatey.__salt__, {"cmd.run_all": mock_run}):
chocolatey.list_()
expected_call = [choco_path, "search", "--limit-output"]
mock_run.assert_called_with(expected_call, python_shell=False)
def test_list_webpi_pre_2_0_0():
mock_version = MagicMock(return_value="1.2.1")
mock_find = MagicMock(return_value=choco_path)
mock_run = MagicMock(return_value={"stdout": "No packages", "retcode": 0})
with patch.object(chocolatey, "chocolatey_version", mock_version), patch.object(
chocolatey, "_find_chocolatey", mock_find
), patch.dict(chocolatey.__salt__, {"cmd.run_all": mock_run}):
chocolatey.list_webpi()
expected_call = [choco_path, "list", "--source", "webpi"]
mock_run.assert_called_with(expected_call, python_shell=False)
def test_list_webpi_post_2_0_0():
mock_version = MagicMock(return_value="2.0.1")
mock_find = MagicMock(return_value=choco_path)
mock_run = MagicMock(return_value={"stdout": "No packages", "retcode": 0})
with patch.object(chocolatey, "chocolatey_version", mock_version), patch.object(
chocolatey, "_find_chocolatey", mock_find
), patch.dict(chocolatey.__salt__, {"cmd.run_all": mock_run}):
chocolatey.list_webpi()
expected_call = [choco_path, "search", "--source", "webpi"]
mock_run.assert_called_with(expected_call, python_shell=False)
def test_list_windowsfeatures_pre_2_0_0():
mock_version = MagicMock(return_value="1.2.1")
mock_find = MagicMock(return_value=choco_path)
mock_run = MagicMock(return_value={"stdout": "No packages", "retcode": 0})
with patch.object(chocolatey, "chocolatey_version", mock_version), patch.object(
chocolatey, "_find_chocolatey", mock_find
), patch.dict(chocolatey.__salt__, {"cmd.run_all": mock_run}):
chocolatey.list_windowsfeatures()
expected_call = [choco_path, "list", "--source", "windowsfeatures"]
mock_run.assert_called_with(expected_call, python_shell=False)
def test_list_windowsfeatures_post_2_0_0():
mock_version = MagicMock(return_value="2.0.1")
mock_find = MagicMock(return_value=choco_path)
mock_run = MagicMock(return_value={"stdout": "No packages", "retcode": 0})
with patch.object(chocolatey, "chocolatey_version", mock_version), patch.object(
chocolatey, "_find_chocolatey", mock_find
), patch.dict(chocolatey.__salt__, {"cmd.run_all": mock_run}):
chocolatey.list_windowsfeatures()
expected_call = [choco_path, "search", "--source", "windowsfeatures"]
mock_run.assert_called_with(expected_call, python_shell=False)

View file

@ -563,6 +563,140 @@ def download_artifacts(ctx: Context, name: str):
vm.download_artifacts()
@vm.command(
name="sync-cache",
arguments={
"key_name": {"help": "The SSH key name."},
"delete": {
"help": "Delete the entries in the cache that don't align with ec2",
"action": "store_true",
},
},
)
def sync_cache(
ctx: Context,
key_name: str = os.environ.get("RUNNER_NAME"), # type: ignore[assignment]
delete: bool = False,
):
"""
Sync the cache
"""
ec2_instances = _filter_instances_by_state(
_get_instances_by_key(ctx, key_name),
{"running"},
)
cached_instances = {}
if STATE_DIR.exists():
for state_path in STATE_DIR.iterdir():
instance_id = (state_path / "instance-id").read_text()
cached_instances[instance_id] = state_path.name
# Find what instances we are missing in our cached states
to_write = {}
to_remove = cached_instances.copy()
for instance in ec2_instances:
if instance.id not in cached_instances:
for tag in instance.tags:
if tag.get("Key") == "vm-name":
to_write[tag.get("Value")] = instance
break
else:
del to_remove[instance.id]
for cached_id, vm_name in to_remove.items():
if delete:
shutil.rmtree(STATE_DIR / vm_name)
log.info(
f"REMOVED {vm_name} ({cached_id.strip()}) from cache at {STATE_DIR / vm_name}"
)
else:
log.info(
f"Would remove {vm_name} ({cached_id.strip()}) from cache at {STATE_DIR / vm_name}"
)
if not delete and to_remove:
log.info("To force the removal of the above cache entries, pass --delete")
for name_tag, vm_instance in to_write.items():
vm_write = VM(ctx=ctx, name=name_tag, region_name=ctx.parser.options.region)
vm_write.instance = vm_instance
vm_write.write_state()
@vm.command(
name="list",
arguments={
"key_name": {"help": "The SSH key name."},
"states": {
"help": "The instance state to filter by.",
"flags": ["-s", "-state"],
"action": "append",
},
},
)
def list_vms(
ctx: Context,
key_name: str = os.environ.get("RUNNER_NAME"), # type: ignore[assignment]
states: set[str] = None,
):
"""
List the vms associated with the given key.
"""
instances = _filter_instances_by_state(
_get_instances_by_key(ctx, key_name),
states,
)
for instance in instances:
vm_state = instance.state["Name"]
ip_addr = instance.public_ip_address
ami = instance.image_id
vm_name = None
for tag in instance.tags:
if tag.get("Key") == "vm-name":
vm_name = tag.get("Value")
break
if vm_name is not None:
sep = "\n "
extra_info = {
"IP": ip_addr,
"AMI": ami,
}
extras = sep + sep.join(
[f"{key}: {value}" for key, value in extra_info.items()]
)
log.info(f"{vm_name} ({vm_state}){extras}")
def _get_instances_by_key(ctx: Context, key_name: str):
if key_name is None:
ctx.exit(1, "We need a key name to filter the instances by.")
ec2 = boto3.resource("ec2", region_name=ctx.parser.options.region)
# First let's get the instances on AWS associated with the key given
filters = [
{"Name": "key-name", "Values": [key_name]},
]
try:
instances = list(
ec2.instances.filter(
Filters=filters,
)
)
except ClientError as exc:
if "RequestExpired" not in str(exc):
raise
ctx.error(str(exc))
ctx.exit(1)
return instances
def _filter_instances_by_state(instances: list[Instance], states: set[str] | None):
if states is None:
return instances
return [instance for instance in instances if instance.state["Name"] in states]
@attr.s(frozen=True, kw_only=True)
class AMIConfig:
ami: str = attr.ib()