Migrate package tests to the main test suite

This commit is contained in:
Megan Wilhite 2023-11-14 13:36:32 -07:00 committed by Daniel Wozniak
parent 98dce76198
commit e2f5e5dbbf
33 changed files with 2805 additions and 0 deletions

View file

View file

@ -0,0 +1,573 @@
import logging
import os
import pathlib
import shutil
import subprocess
import sys
import pytest
import yaml
from pytestskipmarkers.utils import platform
from saltfactories.utils import random_string
from saltfactories.utils.tempfiles import SaltPillarTree, SaltStateTree
import salt.config
from tests.pytests.pkg.support.helpers import (
CODE_DIR,
TESTS_DIR,
ApiRequest,
SaltMaster,
SaltMasterWindows,
SaltPkgInstall,
TestUser,
)
from tests.support.sminion import create_sminion
log = logging.getLogger(__name__)
# Variable defining a FIPS test run or not
FIPS_TESTRUN = os.environ.get("FIPS_TESTRUN", "0") == "1"
@pytest.fixture(scope="session")
def version(install_salt):
"""
get version number from artifact
"""
return install_salt.version
@pytest.fixture(scope="session")
def sminion():
return create_sminion()
@pytest.fixture(scope="session")
def grains(sminion):
return sminion.opts["grains"].copy()
@pytest.fixture(scope="session", autouse=True)
def _system_up_to_date(
grains,
shell,
):
if grains["os_family"] == "Debian":
ret = shell.run("apt", "update")
assert ret.returncode == 0
env = os.environ.copy()
env["DEBIAN_FRONTEND"] = "noninteractive"
ret = shell.run(
"apt",
"upgrade",
"-y",
"-o",
"DPkg::Options::=--force-confdef",
"-o",
"DPkg::Options::=--force-confold",
env=env,
)
assert ret.returncode == 0
elif grains["os_family"] == "Redhat":
ret = shell.run("yum", "update", "-y")
assert ret.returncode == 0
def pytest_addoption(parser):
"""
register argparse-style options and ini-style config values.
"""
test_selection_group = parser.getgroup("Tests Runtime Selection")
test_selection_group.addoption(
"--pkg-system-service",
default=False,
action="store_true",
help="Run the daemons as system services",
)
test_selection_group.addoption(
"--upgrade",
default=False,
action="store_true",
help="Install previous version and then upgrade then run tests",
)
test_selection_group.addoption(
"--downgrade",
default=False,
action="store_true",
help="Install current version and then downgrade to the previous version and run tests",
)
test_selection_group.addoption(
"--no-install",
default=False,
action="store_true",
help="Do not install salt and use a previous install Salt package",
)
test_selection_group.addoption(
"--no-uninstall",
default=False,
action="store_true",
help="Do not uninstall salt packages after test run is complete",
)
test_selection_group.addoption(
"--classic",
default=False,
action="store_true",
help="Test an upgrade from the classic packages.",
)
test_selection_group.addoption(
"--prev-version",
action="store",
help="Test an upgrade from the version specified.",
)
test_selection_group.addoption(
"--use-prev-version",
action="store_true",
help="Tells the test suite to validate the version using the previous version (for downgrades)",
)
test_selection_group.addoption(
"--download-pkgs",
default=False,
action="store_true",
help="Test package download tests",
)
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_setup(item):
"""
Fixtures injection based on markers or test skips based on CLI arguments
"""
if (
str(item.fspath).startswith(str(pathlib.Path(__file__).parent / "download"))
and item.config.getoption("--download-pkgs") is False
):
raise pytest.skip.Exception(
"The package download tests are disabled. Pass '--download-pkgs' to pytest "
"to enable them.",
_use_item_location=True,
)
@pytest.fixture(scope="session")
def salt_factories_root_dir(request, tmp_path_factory):
root_dir = SaltPkgInstall.salt_factories_root_dir(
request.config.getoption("--pkg-system-service")
)
if root_dir is not None:
yield root_dir
else:
if platform.is_darwin():
root_dir = pathlib.Path("/tmp/salt-tests-tmpdir")
root_dir.mkdir(mode=0o777, parents=True, exist_ok=True)
else:
root_dir = tmp_path_factory.mktemp("salt-tests")
try:
yield root_dir
finally:
shutil.rmtree(str(root_dir), ignore_errors=True)
@pytest.fixture(scope="session")
def salt_factories_config(salt_factories_root_dir):
return {
"code_dir": CODE_DIR,
"root_dir": salt_factories_root_dir,
"system_service": True,
}
@pytest.fixture(scope="session")
def install_salt(request, salt_factories_root_dir):
with SaltPkgInstall(
conf_dir=salt_factories_root_dir / "etc" / "salt",
pkg_system_service=request.config.getoption("--pkg-system-service"),
upgrade=request.config.getoption("--upgrade"),
downgrade=request.config.getoption("--downgrade"),
no_uninstall=request.config.getoption("--no-uninstall"),
no_install=request.config.getoption("--no-install"),
classic=request.config.getoption("--classic"),
prev_version=request.config.getoption("--prev-version"),
use_prev_version=request.config.getoption("--use-prev-version"),
) as fixture:
yield fixture
@pytest.fixture(scope="session")
def salt_factories(salt_factories, salt_factories_root_dir):
salt_factories.root_dir = salt_factories_root_dir
return salt_factories
@pytest.fixture(scope="session")
def state_tree():
if platform.is_windows():
file_root = pathlib.Path("C:/salt/srv/salt")
elif platform.is_darwin():
file_root = pathlib.Path("/opt/srv/salt")
else:
file_root = pathlib.Path("/srv/salt")
envs = {
"base": [
str(file_root),
str(TESTS_DIR / "pytests" / "pkg" / "files"),
],
}
tree = SaltStateTree(envs=envs)
test_sls_contents = """
test_foo:
test.succeed_with_changes:
- name: foo
"""
states_sls_contents = """
update:
pkg.installed:
- name: bash
salt_dude:
user.present:
- name: dude
- fullname: Salt Dude
"""
win_states_sls_contents = """
create_empty_file:
file.managed:
- name: C://salt/test/txt
salt_dude:
user.present:
- name: dude
- fullname: Salt Dude
"""
with tree.base.temp_file("test.sls", test_sls_contents), tree.base.temp_file(
"states.sls", states_sls_contents
), tree.base.temp_file("win_states.sls", win_states_sls_contents):
yield tree
@pytest.fixture(scope="session")
def pillar_tree():
"""
Add pillar files
"""
if platform.is_windows():
pillar_root = pathlib.Path("C:/salt/srv/pillar")
elif platform.is_darwin():
pillar_root = pathlib.Path("/opt/srv/pillar")
else:
pillar_root = pathlib.Path("/srv/pillar")
pillar_root.mkdir(mode=0o777, parents=True, exist_ok=True)
tree = SaltPillarTree(
envs={
"base": [
str(pillar_root),
]
},
)
top_file_contents = """
base:
'*':
- test
"""
test_file_contents = """
info: test
"""
with tree.base.temp_file("top.sls", top_file_contents), tree.base.temp_file(
"test.sls", test_file_contents
):
yield tree
@pytest.fixture(scope="module")
def sls(state_tree):
"""
Add an sls file
"""
test_sls_contents = """
test_foo:
test.succeed_with_changes:
- name: foo
"""
states_sls_contents = """
update:
pkg.installed:
- name: bash
salt_dude:
user.present:
- name: dude
- fullname: Salt Dude
"""
win_states_sls_contents = """
create_empty_file:
file.managed:
- name: C://salt/test/txt
salt_dude:
user.present:
- name: dude
- fullname: Salt Dude
"""
with state_tree.base.temp_file(
"tests.sls", test_sls_contents
), state_tree.base.temp_file(
"states.sls", states_sls_contents
), state_tree.base.temp_file(
"win_states.sls", win_states_sls_contents
):
yield
@pytest.fixture(scope="session")
def salt_master(salt_factories, install_salt, state_tree, pillar_tree):
"""
Start up a master
"""
start_timeout = None
# Since the daemons are "packaged" with tiamat, the salt plugins provided
# by salt-factories won't be discovered. Provide the required `*_dirs` on
# the configuration so that they can still be used.
config_defaults = {
"engines_dirs": [
str(salt_factories.get_salt_engines_path()),
],
"log_handlers_dirs": [
str(salt_factories.get_salt_log_handlers_path()),
],
}
if platform.is_darwin():
config_defaults["enable_fqdns_grains"] = False
config_overrides = {
"timeout": 30,
"file_roots": state_tree.as_dict(),
"pillar_roots": pillar_tree.as_dict(),
"rest_cherrypy": {"port": 8000, "disable_ssl": True},
"netapi_enable_clients": ["local"],
"external_auth": {"auto": {"saltdev": [".*"]}},
"fips_mode": FIPS_TESTRUN,
"open_mode": True,
}
test_user = False
master_config = install_salt.config_path / "master"
if master_config.exists():
with salt.utils.files.fopen(master_config) as fp:
data = yaml.safe_load(fp)
if data and "user" in data:
test_user = True
# We are testing a different user, so we need to test the system
# configs, or else permissions will not be correct.
config_overrides["user"] = data["user"]
config_overrides["log_file"] = salt.config.DEFAULT_MASTER_OPTS.get(
"log_file"
)
config_overrides["root_dir"] = salt.config.DEFAULT_MASTER_OPTS.get(
"root_dir"
)
config_overrides["key_logfile"] = salt.config.DEFAULT_MASTER_OPTS.get(
"key_logfile"
)
config_overrides["pki_dir"] = salt.config.DEFAULT_MASTER_OPTS.get(
"pki_dir"
)
config_overrides["api_logfile"] = salt.config.DEFAULT_API_OPTS.get(
"api_logfile"
)
config_overrides["api_pidfile"] = salt.config.DEFAULT_API_OPTS.get(
"api_pidfile"
)
# verify files were set with correct owner/group
verify_files = [
pathlib.Path("/etc", "salt", "pki", "master"),
pathlib.Path("/etc", "salt", "master.d"),
pathlib.Path("/var", "cache", "salt", "master"),
]
for _file in verify_files:
assert _file.owner() == "salt"
assert _file.group() == "salt"
master_script = False
if platform.is_windows():
if install_salt.classic:
master_script = True
if install_salt.relenv:
master_script = True
elif not install_salt.upgrade:
master_script = True
if (
not install_salt.relenv
and install_salt.use_prev_version
and not install_salt.classic
):
master_script = False
if master_script:
salt_factories.system_service = False
salt_factories.generate_scripts = True
scripts_dir = salt_factories.root_dir / "Scripts"
scripts_dir.mkdir(exist_ok=True)
salt_factories.scripts_dir = scripts_dir
python_executable = install_salt.bin_dir / "Scripts" / "python.exe"
if install_salt.classic:
python_executable = install_salt.bin_dir / "python.exe"
if install_salt.relenv:
python_executable = install_salt.install_dir / "Scripts" / "python.exe"
salt_factories.python_executable = python_executable
factory = salt_factories.salt_master_daemon(
random_string("master-"),
defaults=config_defaults,
overrides=config_overrides,
factory_class=SaltMasterWindows,
salt_pkg_install=install_salt,
)
salt_factories.system_service = True
else:
if install_salt.classic and platform.is_darwin():
os.environ["PATH"] += ":/opt/salt/bin"
factory = salt_factories.salt_master_daemon(
random_string("master-"),
defaults=config_defaults,
overrides=config_overrides,
factory_class=SaltMaster,
salt_pkg_install=install_salt,
)
factory.after_terminate(pytest.helpers.remove_stale_master_key, factory)
if test_user:
# Salt factories calls salt.utils.verify.verify_env
# which sets root perms on /etc/salt/pki/master since we are running
# the test suite as root, but we want to run Salt master as salt
# We ensure those permissions where set by the package earlier
subprocess.run(
[
"chown",
"-R",
"salt:salt",
str(pathlib.Path("/etc", "salt", "pki", "master")),
],
check=True,
)
# The engines_dirs is created in .nox path. We need to set correct perms
# for the user running the Salt Master
subprocess.run(
["chown", "-R", "salt:salt", str(CODE_DIR.parent / ".nox")], check=False
)
file_roots = pathlib.Path("/srv/", "salt")
pillar_roots = pathlib.Path("/srv/", "pillar")
for _dir in [file_roots, pillar_roots]:
subprocess.run(["chown", "-R", "salt:salt", str(_dir)], check=False)
with factory.started(start_timeout=start_timeout):
yield factory
@pytest.fixture(scope="session")
def salt_minion(salt_factories, salt_master, install_salt):
"""
Start up a minion
"""
start_timeout = None
minion_id = random_string("minion-")
# Since the daemons are "packaged" with tiamat, the salt plugins provided
# by salt-factories won't be discovered. Provide the required `*_dirs` on
# the configuration so that they can still be used.
config_defaults = {
"engines_dirs": salt_master.config["engines_dirs"].copy(),
"log_handlers_dirs": salt_master.config["log_handlers_dirs"].copy(),
}
if platform.is_darwin():
config_defaults["enable_fqdns_grains"] = False
config_overrides = {
"id": minion_id,
"file_roots": salt_master.config["file_roots"].copy(),
"pillar_roots": salt_master.config["pillar_roots"].copy(),
"fips_mode": FIPS_TESTRUN,
"open_mode": True,
}
if platform.is_windows():
config_overrides[
"winrepo_dir"
] = rf"{salt_factories.root_dir}\srv\salt\win\repo"
config_overrides[
"winrepo_dir_ng"
] = rf"{salt_factories.root_dir}\srv\salt\win\repo_ng"
config_overrides["winrepo_source_dir"] = r"salt://win/repo_ng"
if install_salt.classic and platform.is_windows():
salt_factories.python_executable = None
if install_salt.classic and platform.is_darwin():
os.environ["PATH"] += ":/opt/salt/bin"
factory = salt_master.salt_minion_daemon(
minion_id,
overrides=config_overrides,
defaults=config_defaults,
)
# Salt factories calls salt.utils.verify.verify_env
# which sets root perms on /srv/salt and /srv/pillar since we are running
# the test suite as root, but we want to run Salt master as salt
if not platform.is_windows() and not platform.is_darwin():
file_roots = pathlib.Path("/srv/", "salt")
pillar_roots = pathlib.Path("/srv/", "pillar")
for _dir in [file_roots, pillar_roots]:
subprocess.run(["chown", "-R", "salt:salt", str(_dir)], check=True)
factory.after_terminate(
pytest.helpers.remove_stale_minion_key_pkg, salt_master, factory.id
)
with factory.started(start_timeout=start_timeout):
yield factory
@pytest.fixture(scope="module")
def salt_cli(salt_master):
return salt_master.salt_cli()
@pytest.fixture(scope="module")
def salt_key_cli(salt_master):
return salt_master.salt_key_cli()
@pytest.fixture(scope="module")
def salt_call_cli(salt_minion):
return salt_minion.salt_call_cli()
@pytest.fixture(scope="module")
def test_account(salt_call_cli):
with TestUser(salt_call_cli=salt_call_cli) as account:
yield account
@pytest.fixture(scope="module")
def extras_pypath():
extras_dir = "extras-{}.{}".format(*sys.version_info)
if platform.is_windows():
return pathlib.Path(
os.getenv("ProgramFiles"), "Salt Project", "Salt", extras_dir
)
elif platform.is_darwin():
return pathlib.Path("/opt", "salt", extras_dir)
else:
return pathlib.Path("/opt", "saltstack", "salt", extras_dir)
@pytest.fixture(scope="module")
def extras_pypath_bin(extras_pypath):
return extras_pypath / "bin"
@pytest.fixture(scope="module")
def salt_api(salt_master, install_salt, extras_pypath):
"""
start up and configure salt_api
"""
shutil.rmtree(str(extras_pypath), ignore_errors=True)
start_timeout = None
factory = salt_master.salt_api_daemon()
with factory.started(start_timeout=start_timeout):
yield factory
@pytest.fixture(scope="module")
def api_request(test_account, salt_api):
with ApiRequest(salt_api=salt_api, test_account=test_account) as session:
yield session

View file

View file

@ -0,0 +1,59 @@
import packaging.version
import pytest
from pytestskipmarkers.utils import platform
def test_salt_downgrade(salt_call_cli, install_salt):
"""
Test an upgrade of Salt.
"""
if not install_salt.downgrade:
pytest.skip("Not testing a downgrade, do not run")
is_downgrade_to_relenv = packaging.version.parse(
install_salt.prev_version
) >= packaging.version.parse("3006.0")
if is_downgrade_to_relenv:
original_py_version = install_salt.package_python_version()
# Verify current install version is setup correctly and works
ret = salt_call_cli.run("test.version")
assert ret.returncode == 0
assert packaging.version.parse(ret.data) == packaging.version.parse(
install_salt.artifact_version
)
# Test pip install before a downgrade
dep = "PyGithub==1.56.0"
install = salt_call_cli.run("--local", "pip.install", dep)
assert install.returncode == 0
# Verify we can use the module dependent on the installed package
repo = "https://github.com/saltstack/salt.git"
use_lib = salt_call_cli.run("--local", "github.get_repo_info", repo)
assert "Authentication information could" in use_lib.stderr
# Downgrade Salt to the previous version and test
install_salt.install(downgrade=True)
bin_file = "salt"
if platform.is_windows():
if not is_downgrade_to_relenv:
bin_file = install_salt.install_dir / "salt-call.bat"
else:
bin_file = install_salt.install_dir / "salt-call.exe"
elif platform.is_darwin() and install_salt.classic:
bin_file = install_salt.bin_dir / "salt-call"
ret = install_salt.proc.run(bin_file, "--version")
assert ret.returncode == 0
assert packaging.version.parse(
ret.stdout.strip().split()[1]
) < packaging.version.parse(install_salt.artifact_version)
if is_downgrade_to_relenv:
new_py_version = install_salt.package_python_version()
if new_py_version == original_py_version:
# test pip install after a downgrade
use_lib = salt_call_cli.run("--local", "github.get_repo_info", repo)
assert "Authentication information could" in use_lib.stderr

View file

View file

@ -0,0 +1,559 @@
"""
Test Salt Pkg Downloads
"""
import contextlib
import logging
import os
import pathlib
import shutil
import packaging
import pytest
from pytestskipmarkers.utils import platform
log = logging.getLogger(__name__)
def get_salt_test_commands():
salt_release = get_salt_release()
if platform.is_windows():
if packaging.version.parse(salt_release) > packaging.version.parse("3005"):
salt_test_commands = [
["salt-call.exe", "--local", "test.versions"],
["salt-call.exe", "--local", "grains.items"],
["salt-minion.exe", "--version"],
]
else:
salt_test_commands = [
["salt-call.bat", "--local", "test.versions"],
["salt-call.bat", "--local", "grains.items"],
["salt.bat", "--version"],
["salt-master.bat", "--version"],
["salt-minion.bat", "--version"],
["salt-ssh.bat", "--version"],
["salt-syndic.bat", "--version"],
["salt-api.bat", "--version"],
["salt-cloud.bat", "--version"],
]
else:
salt_test_commands = [
["salt-call", "--local", "test.versions"],
["salt-call", "--local", "grains.items"],
["salt", "--version"],
["salt-master", "--version"],
["salt-minion", "--version"],
["salt-ssh", "--version"],
["salt-syndic", "--version"],
["salt-api", "--version"],
["salt-cloud", "--version"],
]
return salt_test_commands
@pytest.fixture(scope="module")
def root_url(salt_release):
if os.environ.get("SALT_REPO_TYPE", "release") == "staging":
repo_domain = os.environ.get(
"SALT_REPO_DOMAIN_STAGING", "staging.repo.saltproject.io"
)
else:
repo_domain = os.environ.get("SALT_REPO_DOMAIN_RELEASE", "repo.saltproject.io")
if "rc" in salt_release:
salt_path = "salt_rc/salt"
else:
salt_path = "salt"
salt_repo_user = os.environ.get("SALT_REPO_USER")
if salt_repo_user:
log.info(
"SALT_REPO_USER: %s",
salt_repo_user[0] + "*" * (len(salt_repo_user) - 2) + salt_repo_user[-1],
)
salt_repo_pass = os.environ.get("SALT_REPO_PASS")
if salt_repo_pass:
log.info(
"SALT_REPO_PASS: %s",
salt_repo_pass[0] + "*" * (len(salt_repo_pass) - 2) + salt_repo_pass[-1],
)
if salt_repo_user and salt_repo_pass:
repo_domain = f"{salt_repo_user}:{salt_repo_pass}@{repo_domain}"
_root_url = f"https://{repo_domain}/{salt_path}/py3"
log.info("Repository Root URL: %s", _root_url)
return _root_url
@pytest.fixture(scope="module")
def package_type():
return os.environ.get("DOWNLOAD_TEST_PACKAGE_TYPE")
def get_salt_release():
salt_release = os.environ.get("SALT_RELEASE")
pkg_test_type = os.environ.get("PKG_TEST_TYPE", "install")
if salt_release is None:
if pkg_test_type == "download-pkgs":
log.warning(
"Setting salt release to 3006.0rc2 which is probably not what you want."
)
salt_release = "3006.0rc2"
if pkg_test_type == "download-pkgs":
if packaging.version.parse(salt_release) < packaging.version.parse("3006.0rc1"):
log.warning(f"The salt release being tested, {salt_release!r} looks off.")
return salt_release
def get_repo_subpath_params():
current_release = packaging.version.parse(get_salt_release())
params = ["minor", current_release.major]
latest_env_var = os.environ.get("LATEST_SALT_RELEASE")
if latest_env_var is not None:
latest_release = packaging.version.parse(latest_env_var)
if current_release >= latest_release:
log.debug(
f"Running the tests for the latest release since {str(current_release)} >= {str(latest_release)}"
)
params.append("latest")
return params
@pytest.fixture(
scope="module",
params=get_repo_subpath_params(),
)
def repo_subpath(request):
return request.param
@pytest.fixture(scope="module")
def gpg_key_name(salt_release):
if packaging.version.parse(salt_release) > packaging.version.parse("3005"):
return "SALT-PROJECT-GPG-PUBKEY-2023.pub"
return "salt-archive-keyring.gpg"
@pytest.fixture(scope="module")
def salt_release():
yield get_salt_release()
@pytest.fixture(scope="module")
def onedir_install_path(tmp_path_factory):
install_path = tmp_path_factory.mktemp("onedir_install")
yield install_path
shutil.rmtree(install_path, ignore_errors=True)
@pytest.fixture(scope="module")
def _setup_system(
grains,
shell,
root_url,
salt_release,
gpg_key_name,
repo_subpath,
package_type,
tmp_path_factory,
onedir_install_path,
):
downloads_path = tmp_path_factory.mktemp("downloads")
try:
# Windows is a special case, because sometimes we need to uninstall the packages
if grains["os_family"] == "Windows":
with setup_windows(
shell,
root_url=root_url,
salt_release=salt_release,
downloads_path=downloads_path,
repo_subpath=repo_subpath,
package_type=package_type,
onedir_install_path=onedir_install_path,
):
yield
else:
if grains["os_family"] == "MacOS":
setup_macos(
shell,
root_url=root_url,
salt_release=salt_release,
downloads_path=downloads_path,
repo_subpath=repo_subpath,
package_type=package_type,
onedir_install_path=onedir_install_path,
)
elif grains["os"] == "Amazon":
setup_redhat_family(
shell,
os_name=grains["os"].lower(),
os_version=grains["osmajorrelease"],
root_url=root_url,
salt_release=salt_release,
downloads_path=downloads_path,
gpg_key_name=gpg_key_name,
repo_subpath=repo_subpath,
)
elif grains["os"] == "Fedora":
setup_redhat_family(
shell,
os_name=grains["os"].lower(),
os_version=grains["osmajorrelease"],
root_url=root_url,
salt_release=salt_release,
downloads_path=downloads_path,
gpg_key_name=gpg_key_name,
repo_subpath=repo_subpath,
)
elif grains["os"] == "VMware Photon OS":
setup_redhat_family(
shell,
os_name="photon",
os_version=grains["osmajorrelease"],
root_url=root_url,
salt_release=salt_release,
downloads_path=downloads_path,
gpg_key_name=gpg_key_name,
repo_subpath=repo_subpath,
)
elif grains["os_family"] == "RedHat":
setup_redhat_family(
shell,
os_name="redhat",
os_version=grains["osmajorrelease"],
root_url=root_url,
salt_release=salt_release,
downloads_path=downloads_path,
gpg_key_name=gpg_key_name,
repo_subpath=repo_subpath,
)
elif grains["os_family"] == "Debian":
setup_debian_family(
shell,
os_name=grains["os"].lower(),
os_version=grains["osrelease"],
os_codename=grains["oscodename"],
root_url=root_url,
salt_release=salt_release,
downloads_path=downloads_path,
gpg_key_name=gpg_key_name,
repo_subpath=repo_subpath,
package_type=package_type,
onedir_install_path=onedir_install_path,
)
else:
pytest.fail("Don't know how to handle %s", grains["osfinger"])
yield
finally:
shutil.rmtree(downloads_path, ignore_errors=True)
def setup_redhat_family(
shell,
os_name,
os_version,
root_url,
salt_release,
downloads_path,
gpg_key_name,
repo_subpath,
):
arch = os.environ.get("SALT_REPO_ARCH") or "x86_64"
if repo_subpath == "minor":
repo_url_base = (
f"{root_url}/{os_name}/{os_version}/{arch}/{repo_subpath}/{salt_release}"
)
else:
repo_url_base = f"{root_url}/{os_name}/{os_version}/{arch}/{repo_subpath}"
gpg_file_url = f"{root_url}/{os_name}/{os_version}/{arch}/{gpg_key_name}"
try:
pytest.helpers.download_file(gpg_file_url, downloads_path / gpg_key_name)
except Exception as exc: # pylint: disable=broad-except
pytest.fail(f"Failed to download {gpg_file_url}: {exc}")
ret = shell.run("rpm", "--import", str(downloads_path / gpg_key_name), check=False)
if ret.returncode != 0:
pytest.fail("Failed to import gpg key")
repo_file = pytest.helpers.download_file(
f"{repo_url_base}.repo", downloads_path / f"salt-{os_name}.repo"
)
commands = [
("mv", str(repo_file), "/etc/yum.repos.d/salt.repo"),
("yum", "clean", "all" if os_name == "photon" else "expire-cache"),
(
"yum",
"install",
"-y",
"salt-master",
"salt-minion",
"salt-ssh",
"salt-syndic",
"salt-cloud",
"salt-api",
"salt-debuginfo",
),
]
for cmd in commands:
ret = shell.run(*cmd, check=False)
if ret.returncode != 0:
pytest.fail(f"Failed to run '{' '.join(cmd)!r}':\n{ret}")
def setup_debian_family(
shell,
os_name,
os_version,
os_codename,
root_url,
salt_release,
downloads_path,
gpg_key_name,
repo_subpath,
package_type,
onedir_install_path,
):
arch = os.environ.get("SALT_REPO_ARCH") or "amd64"
ret = shell.run("apt-get", "update", "-y", check=False)
if ret.returncode != 0:
pytest.fail(str(ret))
if package_type == "package":
if arch == "aarch64":
arch = "arm64"
elif arch == "x86_64":
arch = "amd64"
if repo_subpath == "minor":
repo_url_base = f"{root_url}/{os_name}/{os_version}/{arch}/{repo_subpath}/{salt_release}"
else:
repo_url_base = f"{root_url}/{os_name}/{os_version}/{arch}/{repo_subpath}"
gpg_file_url = f"{root_url}/{os_name}/{os_version}/{arch}/{gpg_key_name}"
try:
pytest.helpers.download_file(gpg_file_url, downloads_path / gpg_key_name)
except Exception as exc: # pylint: disable=broad-except
pytest.fail(f"Failed to download {gpg_file_url}: {exc}")
salt_sources_path = downloads_path / "salt.list"
salt_sources_path.write_text(
f"deb [signed-by=/usr/share/keyrings/{gpg_key_name} arch={arch}] {repo_url_base} {os_codename} main\n"
)
commands = [
(
"mv",
str(downloads_path / gpg_key_name),
f"/usr/share/keyrings/{gpg_key_name}",
),
(
"mv",
str(salt_sources_path),
"/etc/apt/sources.list.d/salt.list",
),
("apt-get", "install", "-y", "ca-certificates"),
("update-ca-certificates",),
("apt-get", "update"),
(
"apt-get",
"install",
"-y",
"salt-master",
"salt-minion",
"salt-ssh",
"salt-syndic",
"salt-cloud",
"salt-api",
"salt-dbg",
),
]
for cmd in commands:
ret = shell.run(*cmd)
if ret.returncode != 0:
pytest.fail(str(ret))
else:
# We are testing the onedir download
onedir_name = f"salt-{salt_release}-onedir-linux-{arch}.tar.xz"
if repo_subpath == "minor":
repo_url_base = f"{root_url}/onedir/{repo_subpath}/{salt_release}"
else:
repo_url_base = f"{root_url}/onedir/{repo_subpath}"
onedir_url = f"{repo_url_base}/{onedir_name}"
onedir_location = downloads_path / onedir_name
onedir_extracted = onedir_install_path
try:
pytest.helpers.download_file(onedir_url, onedir_location)
except Exception as exc: # pylint: disable=broad-except
pytest.fail(f"Failed to download {onedir_url}: {exc}")
shell.run("tar", "xvf", str(onedir_location), "-C", str(onedir_extracted))
def setup_macos(
shell,
root_url,
salt_release,
downloads_path,
repo_subpath,
package_type,
onedir_install_path,
):
arch = os.environ.get("SALT_REPO_ARCH") or "x86_64"
if package_type == "package":
if packaging.version.parse(salt_release) > packaging.version.parse("3005"):
mac_pkg = f"salt-{salt_release}-py3-{arch}.pkg"
if repo_subpath == "minor":
mac_pkg_url = (
f"{root_url}/macos/{repo_subpath}/{salt_release}/{mac_pkg}"
)
else:
mac_pkg_url = f"{root_url}/macos/{repo_subpath}/{mac_pkg}"
else:
mac_pkg_url = f"{root_url}/macos/{salt_release}/{mac_pkg}"
mac_pkg = f"salt-{salt_release}-macos-{arch}.pkg"
mac_pkg_path = downloads_path / mac_pkg
pytest.helpers.download_file(mac_pkg_url, mac_pkg_path)
ret = shell.run(
"installer",
"-pkg",
str(mac_pkg_path),
"-target",
"/",
check=False,
)
assert ret.returncode == 0, ret
else:
# We are testing the onedir download
onedir_name = f"salt-{salt_release}-onedir-darwin-{arch}.tar.xz"
if repo_subpath == "minor":
repo_url_base = f"{root_url}/onedir/{repo_subpath}/{salt_release}"
else:
repo_url_base = f"{root_url}/onedir/{repo_subpath}"
onedir_url = f"{repo_url_base}/{onedir_name}"
onedir_location = downloads_path / onedir_name
onedir_extracted = onedir_install_path
try:
pytest.helpers.download_file(onedir_url, onedir_location)
except Exception as exc: # pylint: disable=broad-except
pytest.fail(f"Failed to download {onedir_url}: {exc}")
shell.run("tar", "xvf", str(onedir_location), "-C", str(onedir_extracted))
@contextlib.contextmanager
def setup_windows(
shell,
root_url,
salt_release,
downloads_path,
repo_subpath,
package_type,
onedir_install_path,
):
try:
arch = os.environ.get("SALT_REPO_ARCH") or "amd64"
if package_type != "onedir":
root_dir = pathlib.Path(r"C:\Program Files\Salt Project\Salt")
if packaging.version.parse(salt_release) > packaging.version.parse("3005"):
if package_type.lower() == "nsis":
if arch.lower() != "x86":
arch = arch.upper()
win_pkg = f"Salt-Minion-{salt_release}-Py3-{arch}-Setup.exe"
else:
if arch.lower() != "x86":
arch = arch.upper()
win_pkg = f"Salt-Minion-{salt_release}-Py3-{arch}.msi"
if repo_subpath == "minor":
win_pkg_url = (
f"{root_url}/windows/{repo_subpath}/{salt_release}/{win_pkg}"
)
else:
win_pkg_url = f"{root_url}/windows/{repo_subpath}/{win_pkg}"
ssm_bin = root_dir / "ssm.exe"
else:
win_pkg = f"salt-{salt_release}-windows-{arch}.exe"
win_pkg_url = f"{root_url}/windows/{salt_release}/{win_pkg}"
ssm_bin = root_dir / "bin" / "ssm_bin"
pkg_path = downloads_path / win_pkg
pytest.helpers.download_file(win_pkg_url, pkg_path)
if package_type.lower() == "nsis":
ret = shell.run(str(pkg_path), "/start-minion=0", "/S", check=False)
else:
ret = shell.run(
"msiexec", "/qn", "/i", str(pkg_path), 'START_MINION=""'
)
assert ret.returncode == 0, ret
log.debug("Removing installed salt-minion service")
ret = shell.run(
"cmd",
"/c",
str(ssm_bin),
"remove",
"salt-minion",
"confirm",
check=False,
)
assert ret.returncode == 0, ret
else:
# We are testing the onedir download
onedir_name = f"salt-{salt_release}-onedir-windows-{arch}.zip"
if repo_subpath == "minor":
repo_url_base = f"{root_url}/onedir/{repo_subpath}/{salt_release}"
else:
repo_url_base = f"{root_url}/onedir/{repo_subpath}"
onedir_url = f"{repo_url_base}/{onedir_name}"
onedir_location = downloads_path / onedir_name
onedir_extracted = onedir_install_path
try:
pytest.helpers.download_file(onedir_url, onedir_location)
except Exception as exc: # pylint: disable=broad-except
pytest.fail(f"Failed to download {onedir_url}: {exc}")
shell.run("unzip", str(onedir_location), "-d", str(onedir_extracted))
yield
finally:
# We need to uninstall the MSI packages, otherwise they will not install correctly
if package_type.lower() == "msi":
ret = shell.run("msiexec", "/qn", "/x", str(pkg_path))
assert ret.returncode == 0, ret
@pytest.fixture(scope="module")
def install_dir(_setup_system, package_type, onedir_install_path):
if package_type != "onedir":
if platform.is_windows():
return pathlib.Path(
os.getenv("ProgramFiles"), "Salt Project", "Salt"
).resolve()
if platform.is_darwin():
return pathlib.Path("/opt", "salt")
return pathlib.Path("/opt", "saltstack", "salt")
else:
# We are testing the onedir
return onedir_install_path / "salt"
@pytest.fixture(scope="module")
def salt_test_command(request, install_dir):
command = request.param
command[0] = str(install_dir / command[0])
return command
@pytest.mark.parametrize("salt_test_command", get_salt_test_commands(), indirect=True)
def test_download(shell, salt_test_command):
"""
Test downloading of Salt packages and running various commands.
"""
ret = shell.run(*salt_test_command, check=False)
assert ret.returncode == 0, ret

View file

@ -0,0 +1,22 @@
import logging
import pytest
from saltfactories.utils.functional import MultiStateResult
pytestmark = [
pytest.mark.skip_on_windows,
]
log = logging.getLogger(__name__)
def test_check_imports(salt_cli, salt_minion):
"""
Test imports
"""
ret = salt_cli.run("state.sls", "check_imports", minion_tgt=salt_minion.id)
assert ret.returncode == 0
assert ret.data
result = MultiStateResult(raw=ret.data)
for state_ret in result:
assert state_ret.result is True

View file

@ -0,0 +1,61 @@
import logging
import pathlib
import shutil
import textwrap
import pytest
pytestmark = [
pytest.mark.skip_on_windows,
]
log = logging.getLogger(__name__)
@pytest.fixture(autouse=True)
def _skip_on_non_relenv(install_salt):
if not install_salt.relenv:
pytest.skip("This test is for relenv versions of salt")
def test_check_no_import_error(salt_call_cli, salt_master):
"""
Test that we don't have any errors on teardown of python when using a py-rendered sls file
This is a package test because the issue was not reproducible in our normal test suite
"""
init_sls = textwrap.dedent(
"""#!py
def run():
return {
"file_foobar": {
"file.managed": [
{
"name": "/foobar"
},
{
"template": "jinja"
},
{
"context": {
"foobar": "baz",
}
},
{
"source": "salt://breaks/foobar.jinja",
}
]
}
}
"""
)
base_tree = pathlib.Path(salt_master.config["file_roots"]["base"][0])
breaks_tree = base_tree / "breaks"
breaks_tree.mkdir(exist_ok=True)
(breaks_tree / "init.sls").write_text(init_sls)
(breaks_tree / "foobar.jinja").write_text("{{ foobar }}")
output = salt_call_cli.run("state.apply", "breaks", "--output-diff", "test=true")
log.debug(output.stderr)
shutil.rmtree(str(breaks_tree), ignore_errors=True)
assert not output.stderr

View file

@ -0,0 +1,40 @@
import pytest
from pytestskipmarkers.utils import platform
@pytest.mark.skip_on_windows(reason="Linux test only")
def test_services(install_salt, salt_cli, salt_minion):
"""
Check if Services are enabled/disabled
"""
if install_salt.distro_id in ("ubuntu", "debian"):
services_enabled = ["salt-master", "salt-minion", "salt-syndic", "salt-api"]
services_disabled = []
elif install_salt.distro_id in ("centos", "redhat", "amzn", "fedora"):
services_enabled = []
services_disabled = ["salt-master", "salt-minion", "salt-syndic", "salt-api"]
elif install_salt.distro_id == "photon":
if float(install_salt.distro_version) < 5:
services_enabled = []
services_disabled = [
"salt-master",
"salt-minion",
"salt-syndic",
"salt-api",
]
else:
services_enabled = ["salt-master", "salt-minion", "salt-syndic", "salt-api"]
services_disabled = []
elif platform.is_darwin():
services_enabled = ["salt-minion"]
services_disabled = []
else:
pytest.fail(f"Don't know how to handle os_family={install_salt.distro_id}")
for service in services_enabled:
ret = salt_cli.run("service.enabled", service, minion_tgt=salt_minion.id)
assert "true" in ret.stdout
for service in services_disabled:
ret = salt_cli.run("service.disabled", service, minion_tgt=salt_minion.id)
assert "true" in ret.stdout

View file

@ -0,0 +1,25 @@
import subprocess
def test_help(install_salt):
"""
Test --help works for all salt cmds
"""
for cmd in install_salt.binary_paths.values():
cmd = [str(x) for x in cmd]
if len(cmd) > 1 and "shell" in cmd[1]:
# Singlebin build, unable to get the version
continue
if "python" in cmd[0] and len(cmd) == 1:
ret = install_salt.proc.run(
*cmd, "--version", stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
assert "Python" in ret.stdout
else:
ret = install_salt.proc.run(
*cmd, "--help", stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
assert "Usage" in ret.stdout
assert ret.returncode == 0

View file

@ -0,0 +1,46 @@
"""
Tests for logrotate config
"""
import pathlib
import packaging.version
import pytest
pytestmark = [
pytest.mark.skip_unless_on_linux,
]
@pytest.fixture
def logrotate_config_file(grains):
"""
Fixture for logrotate config file path
"""
if grains["os_family"] == "RedHat":
return pathlib.Path("/etc/logrotate.d", "salt")
elif grains["os_family"] == "Debian":
return pathlib.Path("/etc/logrotate.d", "salt-common")
def test_logrotate_config(logrotate_config_file):
"""
Test that logrotate config has been installed in correctly
"""
assert logrotate_config_file.is_file()
assert logrotate_config_file.owner() == "root"
assert logrotate_config_file.group() == "root"
def test_issue_65231_etc_logrotate_salt_dir_removed(install_salt):
"""
Test that /etc/logrotate.d/salt is not a directory
"""
if install_salt.prev_version and packaging.version.parse(
install_salt.prev_version
) <= packaging.version.parse("3006.4"):
pytest.skip("Testing a downgrade to 3006.4, do not run")
path = pathlib.Path("/etc/logrotate.d/salt")
if path.exists():
assert path.is_dir() is False

View file

@ -0,0 +1,138 @@
import os
import pathlib
import subprocess
import packaging.version
import psutil
import pytest
pytestmark = [
pytest.mark.skip_unless_on_windows,
]
@pytest.fixture(autouse=True)
def _skip_on_less_than_3006_1(install_salt):
if packaging.version.parse(install_salt.version) <= packaging.version.parse(
"3006.1"
):
pytest.skip(
"Multi-minion script only available on versions greater than 3006.1"
)
@pytest.fixture
def mm_script(install_salt):
yield install_salt.ssm_bin.parent / "multi-minion.ps1"
@pytest.fixture(scope="function")
def mm_conf(mm_script):
yield pathlib.Path(os.getenv("LocalAppData"), "Salt Project", "Salt", "conf")
subprocess.run(
["powershell", str(mm_script).replace(" ", "' '"), "-d"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
def test_script_present(mm_script):
"""
Ensure the multi-minion.ps1 file is present in the root of the installation
"""
assert mm_script.exists()
def test_install(mm_script, mm_conf):
"""
Install a second minion with default settings. Should create a minion config
file in Local AppData
"""
ret = subprocess.run(
["powershell", str(mm_script).replace(" ", "' '")],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert ret.returncode == 0, ret.stderr
conf_file = mm_conf / "minion"
assert conf_file.exists()
assert conf_file.read_text().find("master: salt") > -1
def test_install_master(mm_script, mm_conf):
"""
Install a second minion and set the master to spongebob
"""
ret = subprocess.run(
["powershell", str(mm_script).replace(" ", "' '"), "-m", "spongebob"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert ret.returncode == 0, ret.stderr
conf_file = mm_conf / "minion"
assert conf_file.exists()
assert conf_file.read_text().find("master: spongebob") > -1
def test_install_prefix(mm_script, mm_conf):
"""
Install a second minion and add a prefix to the minion id
"""
ret = subprocess.run(
["powershell", str(mm_script).replace(" ", "' '"), "-p", "squarepants"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert ret.returncode == 0, ret.stderr
conf_file = mm_conf / "minion"
assert conf_file.exists()
assert conf_file.read_text().find("id: squarepants") > -1
def test_install_log_level(mm_script, mm_conf):
"""
Install a second minion and set the log level in the log file to debug
"""
ret = subprocess.run(
["powershell", str(mm_script).replace(" ", "' '"), "-l", "debug"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert ret.returncode == 0, ret.stderr
conf_file = mm_conf / "minion"
assert conf_file.exists()
assert conf_file.read_text().find("log_level_logfile: debug") > -1
def test_install_start(mm_script, mm_conf):
"""
Install a second minion and start that minion in a hidden process
"""
ret = subprocess.run(
["powershell", str(mm_script).replace(" ", "' '"), "-s"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert ret.returncode == 0, ret.stderr
conf_file = mm_conf / "minion"
assert conf_file.exists()
assert conf_file.read_text().find("master: salt") > -1
found = False
for p in psutil.process_iter(["cmdline", "name"]):
if p.info["name"] and p.info["name"] == "salt-minion.exe":
if f"{mm_conf}" in p.info["cmdline"]:
found = True
assert found is True

View file

@ -0,0 +1,206 @@
import json
import os
import pathlib
import shutil
import subprocess
import pytest
from pytestskipmarkers.utils import platform
@pytest.fixture
def pypath():
if platform.is_windows():
return pathlib.Path(os.getenv("ProgramFiles"), "Salt Project", "Salt")
else:
return pathlib.Path("/opt", "saltstack", "salt", "pypath", "bin")
@pytest.fixture(autouse=True)
def wipe_pydeps(shell, install_salt, extras_pypath):
try:
yield
finally:
# Note, uninstalling anything with an associated script will leave the script.
# This is due to a bug in pip.
for dep in ["pep8", "PyGithub"]:
shell.run(
*(install_salt.binary_paths["pip"] + ["uninstall", "-y", dep]),
)
# Let's remove everything under the extras directory, uninstalling doesn't get dependencies
dirs = []
files = []
for filename in extras_pypath.glob("**/**"):
if filename != extras_pypath and filename.exists():
if filename.is_dir():
dirs.append(filename)
else:
files.append(filename)
for fp in files:
fp.unlink()
for dirname in dirs:
shutil.rmtree(dirname, ignore_errors=True)
def test_pip_install(salt_call_cli, install_salt, shell):
"""
Test pip.install and ensure module can use installed library
"""
dep = "PyGithub==1.56.0"
repo = "https://github.com/saltstack/salt.git"
try:
install = salt_call_cli.run("--local", "pip.install", dep)
assert install.returncode == 0
use_lib = salt_call_cli.run("--local", "github.get_repo_info", repo)
assert "Authentication information could" in use_lib.stderr
finally:
ret = salt_call_cli.run("--local", "pip.uninstall", dep)
assert ret.returncode == 0
use_lib = salt_call_cli.run("--local", "github.get_repo_info", repo)
assert "The github execution module cannot be loaded" in use_lib.stderr
def test_pip_install_extras(shell, install_salt, extras_pypath_bin):
"""
Test salt-pip installs into the correct directory
"""
if not install_salt.relenv:
pytest.skip("The extras directory is only in relenv versions")
dep = "pep8"
extras_keyword = "extras-3"
if platform.is_windows():
check_path = extras_pypath_bin / f"{dep}.exe"
else:
check_path = extras_pypath_bin / dep
install_ret = shell.run(*(install_salt.binary_paths["pip"] + ["install", dep]))
assert install_ret.returncode == 0
ret = shell.run(*(install_salt.binary_paths["pip"] + ["list", "--format=json"]))
assert ret.returncode == 0
assert ret.data # We can parse the JSON output
for pkg in ret.data:
if pkg["name"] == dep:
break
else:
pytest.fail(
f"The {dep!r} package was not found installed. Packages Installed: {ret.data}"
)
show_ret = shell.run(*(install_salt.binary_paths["pip"] + ["show", dep]))
assert show_ret.returncode == 0
assert extras_keyword in show_ret.stdout
assert check_path.exists()
ret = shell.run(str(check_path), "--version")
assert ret.returncode == 0
def demote(user_uid, user_gid):
def result():
# os.setgid does not remove group membership, so we remove them here so they are REALLY non-root
os.setgroups([])
os.setgid(user_gid)
os.setuid(user_uid)
return result
@pytest.mark.skip_on_windows(reason="We can't easily demote users on Windows")
def test_pip_non_root(shell, install_salt, test_account, extras_pypath_bin, pypath):
if install_salt.classic:
pytest.skip("We can install non-root for classic packages")
check_path = extras_pypath_bin / "pep8"
if not install_salt.relenv and not install_salt.classic:
check_path = pypath / "pep8"
# We should be able to issue a --help without being root
ret = subprocess.run(
install_salt.binary_paths["salt"] + ["--help"],
preexec_fn=demote(test_account.uid, test_account.gid),
env=test_account.env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert ret.returncode == 0, ret.stderr
assert "Usage" in ret.stdout
# Let tiamat-pip create the pypath directory for us
ret = subprocess.run(
install_salt.binary_paths["pip"] + ["install", "-h"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert ret.returncode == 0, ret.stderr
# Now, we should still not be able to install as non-root
ret = subprocess.run(
install_salt.binary_paths["pip"] + ["install", "pep8"],
preexec_fn=demote(test_account.uid, test_account.gid),
env=test_account.env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert ret.returncode != 0, ret.stderr
# But we should be able to install as root
ret = subprocess.run(
install_salt.binary_paths["pip"] + ["install", "pep8"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert check_path.exists(), shutil.which("pep8")
assert ret.returncode == 0, ret.stderr
def test_pip_install_salt_extension_in_extras(install_salt, extras_pypath, shell):
"""
Test salt-pip installs into the correct directory and the salt extension
is properly loaded.
"""
if not install_salt.relenv:
pytest.skip("The extras directory is only in relenv versions")
dep = "salt-analytics-framework"
dep_version = "0.1.0"
install_ret = shell.run(
*(install_salt.binary_paths["pip"] + ["install", f"{dep}=={dep_version}"]),
)
assert install_ret.returncode == 0
ret = shell.run(
*(install_salt.binary_paths["pip"] + ["list", "--format=json"]),
)
assert ret.returncode == 0
pkgs_installed = json.loads(ret.stdout.strip())
for pkg in pkgs_installed:
if pkg["name"] == dep:
break
else:
pytest.fail(
f"The {dep!r} package was not found installed. Packages Installed: {pkgs_installed}"
)
show_ret = shell.run(
*(install_salt.binary_paths["pip"] + ["show", dep]),
)
assert show_ret.returncode == 0
assert extras_pypath.joinpath("saf").is_dir()
ret = shell.run(
*(install_salt.binary_paths["minion"] + ["--versions-report"]),
)
assert show_ret.returncode == 0
assert "Salt Extensions" in ret.stdout
assert f"{dep}: {dep_version}" in ret.stdout

View file

@ -0,0 +1,96 @@
import logging
import subprocess
import pytest
log = logging.getLogger(__name__)
pytestmark = [
pytest.mark.skip_on_windows,
]
def test_pip_install(install_salt, salt_call_cli):
"""
Test pip.install and ensure that a package included in the tiamat build can be upgraded
"""
ret = subprocess.run(
install_salt.binary_paths["salt"] + ["--versions-report"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
check=True,
shell=False,
)
assert ret.returncode == 0
possible_upgrades = [
"docker-py",
"msgpack",
"pycparser",
"python-gnupg",
"pyyaml",
"pyzmq",
"jinja2",
]
found_new = False
for dep in possible_upgrades:
get_latest = salt_call_cli.run("--local", "pip.list_all_versions", dep)
if not get_latest.data:
# No information available
continue
dep_version = get_latest.data[-1]
installed_version = None
for line in ret.stdout.splitlines():
if dep in line.lower():
installed_version = line.lower().strip().split(":")[-1].strip()
break
else:
pytest.fail(f"Failed to find {dep} in the versions report output")
if dep_version == installed_version:
log.warning(f"The {dep} dependency is already latest")
else:
found_new = True
break
if found_new:
try:
install = salt_call_cli.run(
"--local", "pip.install", f"{dep}=={dep_version}"
)
assert install
log.warning(install)
# The assert is commented out because pip will actually trigger a failure since
# we're breaking the dependency tree, but, for the purpose of this test, we can
# ignore it.
#
# assert install.returncode == 0
ret = subprocess.run(
install_salt.binary_paths["salt"] + ["--versions-report"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
check=True,
shell=False,
)
assert ret.returncode == 0
for line in ret.stdout.splitlines():
if dep in line.lower():
new_version = line.lower().strip().split(":")[-1].strip()
if new_version == installed_version:
pytest.fail(
f"The newly installed version of {dep} does not show in the versions report"
)
assert new_version == dep_version
break
else:
pytest.fail(f"Failed to find {dep} in the versions report output")
finally:
log.info(f"Uninstalling {dep_version}")
assert salt_call_cli.run(
"--local", "pip.uninstall", f"{dep}=={dep_version}"
)
else:
pytest.skip("Did not find an upgrade version for any of the dependencies")

View file

@ -0,0 +1,36 @@
import sys
import time
import pytest
@pytest.fixture(scope="module")
def pkg_name(salt_call_cli, grains):
if sys.platform.startswith("win"):
ret = salt_call_cli.run("--local", "winrepo.update_git_repos")
assert ret.returncode == 0
attempts = 3
while attempts:
attempts -= 1
ret = salt_call_cli.run("--local", "pkg.refresh_db")
if ret.returncode:
time.sleep(5)
continue
break
else:
pytest.fail("Failed to run 'pkg.refresh_db' 3 times.")
return "putty"
elif grains["os_family"] == "RedHat":
if grains["os"] == "VMware Photon OS":
return "snoopy"
elif grains["osfinger"] == "Amazon Linux-2023":
return "dnf-utils"
return "units"
elif grains["os_family"] == "Debian":
return "ifenslave"
return "figlet"
def test_pkg_install(salt_call_cli, pkg_name):
ret = salt_call_cli.run("--local", "state.single", "pkg.installed", pkg_name)
assert ret.returncode == 0

View file

@ -0,0 +1,49 @@
import subprocess
import pytest
from tests.pytests.pkg.support.helpers import TESTS_DIR
@pytest.fixture
def python_script_bin(install_salt):
# Tiamat builds run scripts via `salt python`
if not install_salt.relenv and not install_salt.classic:
return install_salt.binary_paths["python"][:1] + ["python"]
return install_salt.binary_paths["python"]
@pytest.mark.parametrize("exp_ret,user_arg", [(1, "false"), (0, "true")])
def test_python_script(install_salt, exp_ret, user_arg, python_script_bin):
ret = install_salt.proc.run(
*(
python_script_bin
+ [
str(TESTS_DIR / "pytests" / "pkg" / "files" / "check_python.py"),
user_arg,
]
),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert ret.returncode == exp_ret, ret.stderr
def test_python_script_exception(install_salt, python_script_bin):
ret = install_salt.proc.run(
*(
python_script_bin
+ [
str(TESTS_DIR / "pytests" / "pkg" / "files" / "check_python.py"),
"raise",
]
),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
universal_newlines=True,
)
assert "Exception: test" in ret.stderr

View file

@ -0,0 +1,21 @@
import pytest
pytestmark = [
pytest.mark.skip_on_windows,
]
def test_salt_api(api_request):
"""
Test running a command against the salt api
"""
ret = api_request.post(
"/run",
data={
"client": "local",
"tgt": "*",
"fun": "test.arg",
"arg": ["foo", "bar"],
},
)
assert ret["args"] == ["foo", "bar"]

View file

@ -0,0 +1,59 @@
import pytest
def test_salt_call_local(salt_call_cli):
"""
Test salt-call --local test.ping
"""
ret = salt_call_cli.run("--local", "test.ping")
assert ret.data is True
assert ret.returncode == 0
def test_salt_call(salt_call_cli):
"""
Test salt-call test.ping
"""
ret = salt_call_cli.run("test.ping")
assert ret.data is True
assert ret.returncode == 0
def test_sls(salt_call_cli):
"""
Test calling a sls file
"""
ret = salt_call_cli.run("state.apply", "test")
assert ret.data, ret
sls_ret = ret.data[next(iter(ret.data))]
assert sls_ret["changes"]["testing"]["new"] == "Something pretended to change"
assert ret.returncode == 0
def test_salt_call_local_sys_doc_none(salt_call_cli):
"""
Test salt-call --local sys.doc none
"""
ret = salt_call_cli.run("--local", "sys.doc", "none")
assert not ret.data
assert ret.returncode == 0
def test_salt_call_local_sys_doc_aliases(salt_call_cli):
"""
Test salt-call --local sys.doc aliases
"""
ret = salt_call_cli.run("--local", "sys.doc", "aliases.list_aliases")
assert "aliases.list_aliases" in ret.data
assert ret.returncode == 0
@pytest.mark.skip_on_windows()
def test_salt_call_cmd_run_id_runas(salt_call_cli, test_account, caplog):
"""
Test salt-call --local cmd_run id with runas
"""
ret = salt_call_cli.run("--local", "cmd.run", "id", runas=test_account.username)
assert "Environment could not be retrieved for user" not in caplog.text
assert str(test_account.uid) in ret.stdout
assert str(test_account.gid) in ret.stdout

View file

@ -0,0 +1,38 @@
from sys import platform
import pytest
pytestmark = [
pytest.mark.skip_on_windows,
]
@pytest.fixture
def cat_file(tmp_path):
fp = tmp_path / "cat-file"
fp.write_text(str(fp))
return fp
def test_salt_cmd_run(salt_cli, salt_minion, cat_file):
"""
Test salt cmd.run 'ipconfig' or 'cat <file>'
"""
ret = None
if platform.startswith("win"):
ret = salt_cli.run("cmd.run", "ipconfig", minion_tgt=salt_minion.id)
else:
ret = salt_cli.run("cmd.run", f"cat {str(cat_file)}", minion_tgt=salt_minion.id)
assert ret
assert ret.stdout
def test_salt_list_users(salt_cli, salt_minion):
"""
Test salt user.list_users
"""
ret = salt_cli.run("user.list_users", minion_tgt=salt_minion.id)
if platform.startswith("win"):
assert "Administrator" in ret.stdout
else:
assert "root" in ret.stdout

View file

@ -0,0 +1,41 @@
import pytest
pytestmark = [
pytest.mark.skip_on_windows,
]
def test_grains_items(salt_cli, salt_minion):
"""
Test grains.items
"""
ret = salt_cli.run("grains.items", minion_tgt=salt_minion.id)
assert ret.data, ret
assert "osrelease" in ret.data
def test_grains_item_os(salt_cli, salt_minion):
"""
Test grains.item os
"""
ret = salt_cli.run("grains.item", "os", minion_tgt=salt_minion.id)
assert ret.data, ret
assert "os" in ret.data
def test_grains_item_pythonversion(salt_cli, salt_minion):
"""
Test grains.item pythonversion
"""
ret = salt_cli.run("grains.item", "pythonversion", minion_tgt=salt_minion.id)
assert ret.data, ret
assert "pythonversion" in ret.data
def test_grains_setval_key_val(salt_cli, salt_minion):
"""
Test grains.setval key val
"""
ret = salt_cli.run("grains.setval", "key", "val", minion_tgt=salt_minion.id)
assert ret.data, ret
assert "key" in ret.data

View file

@ -0,0 +1,14 @@
import pytest
pytestmark = [
pytest.mark.skip_on_windows,
]
def test_salt_key(salt_key_cli, salt_minion):
"""
Test running salt-key -L
"""
ret = salt_key_cli.run("-L")
assert ret.data
assert salt_minion.id in ret.data["minions"]

View file

@ -0,0 +1,26 @@
import pytest
pytestmark = [
pytest.mark.skip_on_windows,
]
def test_salt_minion_ping(salt_cli, salt_minion):
"""
Test running a command against a targeted minion
"""
ret = salt_cli.run("test.ping", minion_tgt=salt_minion.id)
assert ret.returncode == 0
assert ret.data is True
def test_salt_minion_setproctitle(salt_cli, salt_minion):
"""
Test that setproctitle is working
for the running Salt minion
"""
ret = salt_cli.run(
"ps.pgrep", "MinionProcessManager", full=True, minion_tgt=salt_minion.id
)
assert ret.returncode == 0
assert ret.data != ""

View file

@ -0,0 +1,19 @@
import pytest
pytestmark = [
pytest.mark.skip_on_windows,
]
@pytest.mark.parametrize("output_fmt", ["yaml", "json"])
def test_salt_output(salt_cli, salt_minion, output_fmt):
"""
Test --output
"""
ret = salt_cli.run(
f"--output={output_fmt}", "test.fib", "7", minion_tgt=salt_minion.id
)
if output_fmt == "json":
assert 13 in ret.data
else:
ret.stdout.matcher.fnmatch_lines(["*- 13*"])

View file

@ -0,0 +1,13 @@
import pytest
pytestmark = [
pytest.mark.skip_on_windows,
]
def test_salt_pillar(salt_cli, salt_minion):
"""
Test pillar.items
"""
ret = salt_cli.run("pillar.items", minion_tgt=salt_minion.id)
assert "info" in ret.data

View file

@ -0,0 +1,24 @@
import sys
import pytest
pytestmark = [
pytest.mark.skip_on_windows,
]
def test_salt_state_file(salt_cli, salt_minion):
"""
Test state file
"""
if sys.platform.startswith("win"):
ret = salt_cli.run("state.apply", "win_states", minion_tgt=salt_minion.id)
else:
ret = salt_cli.run("state.apply", "states", minion_tgt=salt_minion.id)
assert ret.data, ret
if ret.stdout and "Minion did not return" in ret.stdout:
pytest.skip("Skipping test, state took too long to apply")
sls_ret = ret.data[next(iter(ret.data))]
assert "changes" in sls_ret
assert "name" in sls_ret

View file

@ -0,0 +1,38 @@
import pathlib
import pytest
@pytest.mark.skip_on_windows
@pytest.mark.skip_if_binaries_missing("ufw")
def test_salt_ufw(salt_master, salt_call_cli, install_salt):
"""
Test salt.ufw for Debian/Ubuntu salt-master
"""
if install_salt.distro_id not in ("debian", "ubuntu"):
pytest.skip("Only tests Debian / Ubuntu packages")
# check that the salt_master is running
assert salt_master.is_running()
ufw_master_path = pathlib.Path("/etc/ufw/applications.d/salt.ufw")
assert ufw_master_path.exists()
assert ufw_master_path.is_file()
ufw_list_cmd = "/usr/sbin/ufw app list"
ret = salt_call_cli.run("--local", "cmd.run", ufw_list_cmd)
assert "Available applications" in ret.stdout
assert "Salt" in ret.stdout
ufw_upd_cmd = "/usr/sbin/ufw app update Salt"
ret = salt_call_cli.run("--local", "cmd.run", ufw_upd_cmd)
assert ret.returncode == 0
expected_info = """Profile: Salt
Title: salt
Description: fast and powerful configuration management and remote
execution
Ports:
4505,4506/tcp"""
ufw_info_cmd = "/usr/sbin/ufw app info Salt"
ret = salt_call_cli.run("--local", "cmd.run", ufw_info_cmd)
assert expected_info in ret.data

View file

@ -0,0 +1,359 @@
import os
import pathlib
import subprocess
import sys
import packaging.version
import psutil
import pytest
from saltfactories.utils.tempfiles import temp_directory
pytestmark = [
pytest.mark.skip_on_windows,
pytest.mark.skip_on_darwin,
]
@pytest.fixture
def pkg_paths():
"""
Paths created by package installs
"""
paths = [
"/etc/salt",
"/var/cache/salt",
"/var/log/salt",
"/var/run/salt",
"/opt/saltstack/salt",
]
return paths
@pytest.fixture
def pkg_paths_salt_user():
"""
Paths created by package installs and owned by salt user
"""
return [
"/etc/salt/cloud.deploy.d",
"/var/log/salt/cloud",
"/opt/saltstack/salt/lib/python{}.{}/site-packages/salt/cloud/deploy".format(
*sys.version_info
),
"/etc/salt/pki/master",
"/etc/salt/master.d",
"/var/log/salt/master",
"/var/log/salt/api",
"/var/log/salt/key",
"/var/cache/salt/master",
"/var/run/salt/master",
]
@pytest.fixture
def pkg_paths_salt_user_exclusions():
"""
Exclusions from paths created by package installs and owned by salt user
"""
paths = [
"/var/cache/salt/master/.root_key" # written by salt, salt-run and salt-key as root
]
return paths
@pytest.fixture(autouse=True)
def _skip_on_non_relenv(install_salt):
if not install_salt.relenv:
pytest.skip("The salt user only exists on relenv versions of salt")
def test_salt_user_master(salt_master, install_salt):
"""
Test the correct user is running the Salt Master
"""
match = False
for proc in psutil.Process(salt_master.pid).children():
assert proc.username() == "salt"
match = True
assert match
def test_salt_user_home(install_salt):
"""
Test the salt user's home is /opt/saltstack/salt
"""
proc = subprocess.run(
["getent", "passwd", "salt"], check=False, capture_output=True
)
assert proc.returncode == 0
home = ""
try:
home = proc.stdout.decode().split(":")[5]
except Exception: # pylint: disable=broad-except
pass
assert home == "/opt/saltstack/salt"
def test_salt_user_group(install_salt):
"""
Test the salt user is in the salt group
"""
proc = subprocess.run(["id", "salt"], check=False, capture_output=True)
assert proc.returncode == 0
in_group = False
try:
for group in proc.stdout.decode().split(" "):
if "salt" in group:
in_group = True
except Exception: # pylint: disable=broad-except
pass
assert in_group is True
def test_salt_user_shell(install_salt):
"""
Test the salt user's login shell
"""
proc = subprocess.run(
["getent", "passwd", "salt"], check=False, capture_output=True
)
assert proc.returncode == 0
shell = ""
shell_exists = False
try:
shell = proc.stdout.decode().split(":")[6].strip()
shell_exists = pathlib.Path(shell).exists()
except Exception: # pylint: disable=broad-except
pass
assert shell_exists is True
def test_pkg_paths(
install_salt, pkg_paths, pkg_paths_salt_user, pkg_paths_salt_user_exclusions
):
"""
Test package paths ownership
"""
if packaging.version.parse(install_salt.version) <= packaging.version.parse(
"3006.4"
):
pytest.skip("Package path ownership was changed in salt 3006.4")
salt_user_subdirs = []
for _path in pkg_paths:
pkg_path = pathlib.Path(_path)
assert pkg_path.exists()
for dirpath, sub_dirs, files in os.walk(pkg_path):
path = pathlib.Path(dirpath)
# Directories owned by salt:salt or their subdirs/files
if (
str(path) in pkg_paths_salt_user or str(path) in salt_user_subdirs
) and str(path) not in pkg_paths_salt_user_exclusions:
assert path.owner() == "salt"
assert path.group() == "salt"
salt_user_subdirs.extend(
[str(path.joinpath(sub_dir)) for sub_dir in sub_dirs]
)
# Individual files owned by salt user
for file in files:
file_path = path.joinpath(file)
if str(file_path) not in pkg_paths_salt_user_exclusions:
assert file_path.owner() == "salt"
# Directories owned by root:root
else:
assert path.owner() == "root"
assert path.group() == "root"
for file in files:
file_path = path.joinpath(file)
# Individual files owned by salt user
if str(file_path) in pkg_paths_salt_user:
assert file_path.owner() == "salt"
else:
assert file_path.owner() == "root"
assert file_path.group() == "root"
@pytest.mark.skip_if_binaries_missing("logrotate")
def test_paths_log_rotation(
salt_master, salt_minion, salt_call_cli, install_salt, test_account
):
"""
Test the correct ownership is assigned when log rotation occurs
Change the user in the Salt Master, chage ownership, force logrotation
Check ownership and premissions.
Assumes test_pkg_paths successful
"""
if packaging.version.parse(install_salt.version) <= packaging.version.parse(
"3006.4"
):
pytest.skip("Package path ownership was changed in salt 3006.4")
if install_salt.distro_id not in ("centos", "redhat", "amzn", "fedora"):
pytest.skip(
"Only tests RedHat family packages till logrotation paths are resolved on Ubuntu/Debian, see issue 65231"
)
# check that the salt_master is running
assert salt_master.is_running()
match = False
for proc in psutil.Process(salt_master.pid).children():
assert proc.username() == "salt"
match = True
assert match
# Paths created by package installs with adjustment for current conf_dir /etc/salt
log_pkg_paths = [
install_salt.conf_dir, # "bkup0"
"/var/cache/salt", # "bkup1"
"/var/log/salt", # "bkup2"
"/var/run/salt", # "bkup3"
"/opt/saltstack/salt", # "bkup4"
]
# backup those about to change
bkup_count = 0
bkup_count_max = 5
with temp_directory("bkup0") as temp_dir_path_0:
with temp_directory("bkup1") as temp_dir_path_1:
with temp_directory("bkup2") as temp_dir_path_2:
with temp_directory("bkup3") as temp_dir_path_3:
with temp_directory("bkup4") as temp_dir_path_4:
assert temp_dir_path_0.is_dir()
assert temp_dir_path_1.is_dir()
assert temp_dir_path_2.is_dir()
assert temp_dir_path_3.is_dir()
assert temp_dir_path_4.is_dir()
# stop the salt_master, so can change user
with salt_master.stopped():
assert salt_master.is_running() is False
for _path in log_pkg_paths:
if bkup_count == 0:
cmd_to_run = (
f"cp -a {_path}/* {str(temp_dir_path_0)}/"
)
elif bkup_count == 1:
cmd_to_run = (
f"cp -a {_path}/* {str(temp_dir_path_1)}/"
)
elif bkup_count == 2:
cmd_to_run = (
f"cp -a {_path}/* {str(temp_dir_path_2)}/"
)
elif bkup_count == 3:
cmd_to_run = (
f"cp -a {_path}/* {str(temp_dir_path_3)}/"
)
elif bkup_count == 4:
cmd_to_run = (
f"cp -a {_path}/* {str(temp_dir_path_4)}/"
)
elif bkup_count > 5:
assert bkupcount < bkup_count_max # force assertion
ret = salt_call_cli.run(
"--local", "cmd.run", cmd_to_run
)
bkup_count += 1
assert ret.returncode == 0
# change the user in the master's config file.
ret = salt_call_cli.run(
"--local",
"file.replace",
f"{install_salt.conf_dir}/master",
"user: salt",
f"user: {test_account.username}",
"flags=['IGNORECASE']",
"append_if_not_found=True",
)
assert ret.returncode == 0
# change ownership of appropriate paths to user
for _path in log_pkg_paths:
chg_ownership_cmd = (
f"chown -R {test_account.username} {_path}"
)
ret = salt_call_cli.run(
"--local", "cmd.run", chg_ownership_cmd
)
assert ret.returncode == 0
# restart the salt_master
with salt_master.started():
assert salt_master.is_running() is True
# ensure some data in files
log_files_list = [
"/var/log/salt/api",
"/var/log/salt/key",
"/var/log/salt/master",
]
for _path in log_files_list:
log_path = pathlib.Path(_path)
assert log_path.exists()
with log_path.open("a") as f:
f.write("This is a log rotation test\n")
# force log rotation
logr_conf_file = "/etc/logrotate.d/salt"
logr_conf_path = pathlib.Path(logr_conf_file)
if not logr_conf_path.exists():
logr_conf_file = "/etc/logrotate.conf"
logr_conf_path = pathlib.Path(logr_conf_file)
assert logr_conf_path.exists()
# force log rotation
log_rotate_cmd = f"logrotate -f {logr_conf_file}"
ret = salt_call_cli.run(
"--local", "cmd.run", log_rotate_cmd
)
assert ret.returncode == 0
for _path in log_files_list:
log_path = pathlib.Path(_path)
assert log_path.exists()
assert log_path.owner() == test_account.username
assert log_path.stat().st_mode & 0o7777 == 0o640
# cleanup
assert salt_master.is_running() is False
# change the user in the master's config file.
ret = salt_call_cli.run(
"--local",
"file.replace",
f"{install_salt.conf_dir}/master",
f"user: {test_account.username}",
"user: salt",
"flags=['IGNORECASE']",
"append_if_not_found=True",
)
assert ret.returncode == 0
# restore from backed up
bkup_count = 0
for _path in log_pkg_paths:
if bkup_count == 0:
cmd_to_run = f"cp -a --force {str(temp_dir_path_0)}/* {_path}/"
elif bkup_count == 1:
cmd_to_run = f"cp -a --force {str(temp_dir_path_1)}/* {_path}/"
elif bkup_count == 2:
cmd_to_run = f"cp -a --force {str(temp_dir_path_2)}/* {_path}/"
elif bkup_count == 3:
cmd_to_run = f"cp -a --force {str(temp_dir_path_3)}/* {_path}/"
elif bkup_count == 4:
# use --update since /opt/saltstack/salt and would get SIGSEGV since mucking with running code
cmd_to_run = f"cp -a --update --force {str(temp_dir_path_4)}/* {_path}/"
elif bkup_count > 5:
assert bkupcount < bkup_count_max # force assertion
ret = salt_call_cli.run(
"--local", "cmd.run", cmd_to_run
)
bkup_count += 1
assert ret.returncode == 0

View file

@ -0,0 +1,15 @@
import os
import pytest
pytestmark = [
pytest.mark.skip_unless_on_windows,
]
def test_ssm_present(install_salt):
"""
The ssm.exe binary needs to be present in both the zip and the exe/msi
builds
"""
assert os.path.exists(install_salt.ssm_bin)

View file

@ -0,0 +1,42 @@
import subprocess
import pytest
pytestmark = [
pytest.mark.skip_on_windows(reason="Linux test only"),
]
@pytest.mark.usefixtures("salt_minion")
def test_system_config(grains):
"""
Test system config
"""
if grains["os_family"] == "RedHat":
if grains["osfinger"] in (
"CentOS Stream-8",
"CentOS Linux-8",
"CentOS Stream-9",
"Fedora Linux-36",
"VMware Photon OS-3",
"VMware Photon OS-4",
"VMware Photon OS-5",
"Amazon Linux-2023",
):
expected_retcode = 0
else:
expected_retcode = 1
ret = subprocess.call(
"systemctl show -p ${config} salt-minion.service", shell=True
)
assert ret == expected_retcode
elif grains["os_family"] == "Debian":
if grains["osfinger"] == "Debian-9":
expected_retcode = 1
else:
expected_retcode = 0
ret = subprocess.call(
"systemctl show -p ${config} salt-minion.service", shell=True
)
assert ret == expected_retcode

View file

@ -0,0 +1,142 @@
import os.path
import pathlib
import re
import subprocess
import pytest
from pytestskipmarkers.utils import platform
@pytest.mark.skip_on_windows
def test_salt_version(version, install_salt):
"""
Test version output from salt --version
"""
test_bin = os.path.join(*install_salt.binary_paths["salt"])
ret = install_salt.proc.run(test_bin, "--version")
actual = ret.stdout.strip().split(" ")[:2]
expected = ["salt", version]
assert actual == expected
@pytest.mark.skip_on_windows
def test_salt_versions_report_master(install_salt):
"""
Test running --versions-report on master
"""
if not install_salt.relenv and not install_salt.classic:
pytest.skip("Unable to get the python version dynamically from tiamat builds")
test_bin = os.path.join(*install_salt.binary_paths["master"])
python_bin = os.path.join(*install_salt.binary_paths["python"])
ret = install_salt.proc.run(test_bin, "--versions-report")
ret.stdout.matcher.fnmatch_lines(["*Salt Version:*"])
py_version = subprocess.run(
[str(python_bin), "--version"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
).stdout
py_version = py_version.decode().strip().replace(" ", ": ")
ret.stdout.matcher.fnmatch_lines([f"*{py_version}*"])
@pytest.mark.skip_on_windows
def test_salt_versions_report_minion(salt_cli, salt_minion):
"""
Test running test.versions_report on minion
"""
# Make sure the minion is running
assert salt_minion.is_running()
# Make sure we can ping the minion ...
ret = salt_cli.run(
"--timeout=240", "test.ping", minion_tgt=salt_minion.id, _timeout=240
)
assert ret.returncode == 0
assert ret.data is True
ret = salt_cli.run(
"--hard-crash",
"--failhard",
"--timeout=240",
"test.versions_report",
minion_tgt=salt_minion.id,
_timeout=240,
)
ret.stdout.matcher.fnmatch_lines(["*Salt Version:*"])
@pytest.mark.parametrize(
"binary", ["master", "cloud", "syndic", "minion", "call", "api"]
)
def test_compare_versions(version, binary, install_salt):
"""
Test compare versions
"""
if binary in install_salt.binary_paths:
ret = install_salt.proc.run(
*install_salt.binary_paths[binary],
"--version",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
ret.stdout.matcher.fnmatch_lines([f"*{version}*"])
else:
if platform.is_windows():
pytest.skip(f"Binary not available on windows: {binary}")
pytest.fail(
f"Platform is not Windows and yet the binary {binary!r} is not available"
)
@pytest.mark.skip_unless_on_darwin()
@pytest.mark.parametrize(
"symlink",
[
# We can't create a salt symlink because there is a salt directory
"salt",
"salt-api",
"salt-call",
"salt-cloud",
"salt-cp",
"salt-key",
"salt-master",
"salt-minion",
"salt-proxy",
"salt-run",
"spm",
"salt-ssh",
"salt-syndic",
],
)
def test_symlinks_created(version, symlink, install_salt):
"""
Test symlinks created
"""
if install_salt.classic:
pytest.skip("Symlinks not created for classic macos builds, we adjust the path")
if not install_salt.relenv and symlink == "spm":
symlink = "salt-spm"
ret = install_salt.proc.run(pathlib.Path("/usr/local/sbin") / symlink, "--version")
ret.stdout.matcher.fnmatch_lines([f"*{version}*"])
@pytest.mark.skip_on_windows()
def test_compare_pkg_versions_redhat_rc(version, install_salt):
"""
Test compare pkg versions for redhat RC packages. A tilde should be included
in RC Packages and it should test to be a lower version than a non RC
package of the same version. For example, v3004~rc1 should be less than
v3004.
"""
if install_salt.distro_id not in ("centos", "redhat", "amzn", "fedora", "photon"):
pytest.skip("Only tests rpm packages")
pkg = [x for x in install_salt.pkgs if "rpm" in x]
if not pkg:
pytest.skip("Not testing rpm packages")
pkg = pkg[0].split("/")[-1]
if "rc" not in ".".join(pkg.split(".")[:2]):
pytest.skip("Not testing an RC package")
assert "~" in pkg
comp_pkg = pkg.split("~")[0]
ret = install_salt.proc.run("rpmdev-vercmp", pkg, comp_pkg)
ret.stdout.matcher.fnmatch_lines([f"{pkg} < {comp_pkg}"])

View file

View file

@ -0,0 +1,44 @@
import packaging.version
import pytest
def test_salt_upgrade(salt_call_cli, install_salt):
"""
Test an upgrade of Salt.
"""
if not install_salt.upgrade:
pytest.skip("Not testing an upgrade, do not run")
if install_salt.relenv:
original_py_version = install_salt.package_python_version()
# Verify previous install version is setup correctly and works
ret = salt_call_cli.run("test.version")
assert ret.returncode == 0
assert packaging.version.parse(ret.data) < packaging.version.parse(
install_salt.artifact_version
)
# Test pip install before an upgrade
dep = "PyGithub==1.56.0"
install = salt_call_cli.run("--local", "pip.install", dep)
assert install.returncode == 0
# Verify we can use the module dependent on the installed package
repo = "https://github.com/saltstack/salt.git"
use_lib = salt_call_cli.run("--local", "github.get_repo_info", repo)
assert "Authentication information could" in use_lib.stderr
# Upgrade Salt from previous version and test
install_salt.install(upgrade=True)
ret = salt_call_cli.run("test.version")
assert ret.returncode == 0
assert packaging.version.parse(ret.data) == packaging.version.parse(
install_salt.artifact_version
)
if install_salt.relenv:
new_py_version = install_salt.package_python_version()
if new_py_version == original_py_version:
# test pip install after an upgrade
use_lib = salt_call_cli.run("--local", "github.get_repo_info", repo)