mirror of
https://github.com/saltstack/salt.git
synced 2025-04-10 14:51:40 +00:00
5268 lines
188 KiB
Python
5268 lines
188 KiB
Python
"""
|
|
Tests for the file state
|
|
"""
|
|
import errno
|
|
import filecmp
|
|
import logging
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import shutil
|
|
import stat
|
|
import sys
|
|
import tempfile
|
|
import textwrap
|
|
|
|
import pytest
|
|
import salt.serializers.configparser
|
|
import salt.serializers.plist
|
|
import salt.utils.atomicfile
|
|
import salt.utils.data
|
|
import salt.utils.files
|
|
import salt.utils.json
|
|
import salt.utils.path
|
|
import salt.utils.platform
|
|
import salt.utils.stringutils
|
|
from salt.utils.versions import LooseVersion as _LooseVersion
|
|
from tests.support.case import ModuleCase
|
|
from tests.support.helpers import (
|
|
Webserver,
|
|
dedent,
|
|
requires_system_grains,
|
|
with_system_user_and_group,
|
|
with_tempdir,
|
|
with_tempfile,
|
|
)
|
|
from tests.support.mixins import SaltReturnAssertsMixin
|
|
from tests.support.runtests import RUNTIME_VARS
|
|
from tests.support.unit import skipIf
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
HAS_PWD = True
|
|
try:
|
|
import pwd
|
|
except ImportError:
|
|
HAS_PWD = False
|
|
|
|
HAS_GRP = True
|
|
try:
|
|
import grp
|
|
except ImportError:
|
|
HAS_GRP = False
|
|
|
|
|
|
IS_WINDOWS = salt.utils.platform.is_windows()
|
|
|
|
BINARY_FILE = b"GIF89a\x01\x00\x01\x00\x80\x00\x00\x05\x04\x04\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"
|
|
|
|
TEST_SYSTEM_USER = "test_system_user"
|
|
TEST_SYSTEM_GROUP = "test_system_group"
|
|
|
|
DEFAULT_ENDING = salt.utils.stringutils.to_bytes(os.linesep)
|
|
|
|
|
|
def _test_managed_file_mode_keep_helper(testcase, local=False):
|
|
"""
|
|
DRY helper function to run the same test with a local or remote path
|
|
"""
|
|
name = testcase.tmp_dir / "scene33"
|
|
testcase.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
grail_fs_path = os.path.join(RUNTIME_VARS.BASE_FILES, "grail", "scene33")
|
|
grail = "salt://grail/scene33" if not local else grail_fs_path
|
|
|
|
# Get the current mode so that we can put the file back the way we
|
|
# found it when we're done.
|
|
grail_fs_mode = int(testcase.run_function("file.get_mode", [grail_fs_path]), 8)
|
|
initial_mode = 0o770
|
|
new_mode_1 = 0o600
|
|
new_mode_2 = 0o644
|
|
|
|
# Set the initial mode, so we can be assured that when we set the mode
|
|
# to "keep", we're actually changing the permissions of the file to the
|
|
# new mode.
|
|
ret = testcase.run_state(
|
|
"file.managed", name=str(name), mode=oct(initial_mode), source=grail,
|
|
)
|
|
|
|
if IS_WINDOWS:
|
|
testcase.assertSaltFalseReturn(ret)
|
|
return
|
|
|
|
testcase.assertSaltTrueReturn(ret)
|
|
|
|
try:
|
|
# Update the mode on the fileserver (pass 1)
|
|
os.chmod(grail_fs_path, new_mode_1)
|
|
ret = testcase.run_state(
|
|
"file.managed", name=str(name), mode="keep", source=grail,
|
|
)
|
|
testcase.assertSaltTrueReturn(ret)
|
|
managed_mode = stat.S_IMODE(name.stat().st_mode)
|
|
testcase.assertEqual(oct(managed_mode), oct(new_mode_1))
|
|
# Update the mode on the fileserver (pass 2)
|
|
# This assures us that if the file in file_roots was originally set
|
|
# to the same mode as new_mode_1, we definitely get an updated mode
|
|
# this time.
|
|
os.chmod(grail_fs_path, new_mode_2)
|
|
ret = testcase.run_state(
|
|
"file.managed", name=str(name), mode="keep", source=grail,
|
|
)
|
|
testcase.assertSaltTrueReturn(ret)
|
|
managed_mode = stat.S_IMODE(name.stat().st_mode)
|
|
testcase.assertEqual(oct(managed_mode), oct(new_mode_2))
|
|
finally:
|
|
# Set the mode of the file in the file_roots back to what it
|
|
# originally was.
|
|
os.chmod(grail_fs_path, grail_fs_mode)
|
|
|
|
|
|
@pytest.mark.windows_whitelisted
|
|
class FileTest(ModuleCase, SaltReturnAssertsMixin):
|
|
"""
|
|
Validate the file state
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.tmp_dir = pathlib.Path(tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)).resolve()
|
|
|
|
def _reline(path, ending=DEFAULT_ENDING):
|
|
"""
|
|
Normalize the line endings of a file.
|
|
"""
|
|
with salt.utils.files.fopen(path, "rb") as fhr:
|
|
lines = fhr.read().splitlines()
|
|
with salt.utils.atomicfile.atomic_open(path, "wb") as fhw:
|
|
for line in lines:
|
|
fhw.write(line + ending)
|
|
|
|
destpath = os.path.join(RUNTIME_VARS.BASE_FILES, "testappend", "firstif")
|
|
_reline(destpath)
|
|
destpath = os.path.join(RUNTIME_VARS.BASE_FILES, "testappend", "secondif")
|
|
_reline(destpath)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
salt.utils.files.rm_rf(str(cls.tmp_dir))
|
|
|
|
def _delete_file(self, path):
|
|
try:
|
|
os.remove(path)
|
|
except OSError as exc:
|
|
if exc.errno != errno.ENOENT:
|
|
log.error("Failed to remove %s: %s", path, exc)
|
|
|
|
def tearDown(self):
|
|
"""
|
|
remove files created in previous tests
|
|
"""
|
|
user = "salt"
|
|
if user in str(self.run_function("user.list_users")):
|
|
self.run_function("user.delete", [user])
|
|
|
|
def test_symlink(self):
|
|
"""
|
|
file.symlink
|
|
"""
|
|
name = self.tmp_dir / "symlink"
|
|
tgt = self.tmp_dir / "target"
|
|
|
|
# Windows must have a source directory to link to
|
|
if IS_WINDOWS and not tgt.is_dir():
|
|
tgt.mkdir()
|
|
|
|
# Windows cannot create a symlink if it already exists
|
|
if IS_WINDOWS and name.is_symlink():
|
|
name.unlink()
|
|
|
|
ret = self.run_state("file.symlink", name=str(name), target=str(tgt))
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
def test_test_symlink(self):
|
|
"""
|
|
file.symlink test interface
|
|
"""
|
|
name = self.tmp_dir / "symlink2"
|
|
tgt = self.tmp_dir / "target2"
|
|
ret = self.run_state("file.symlink", test=True, name=str(name), target=str(tgt))
|
|
self.assertSaltNoneReturn(ret)
|
|
|
|
def test_absent_file(self):
|
|
"""
|
|
file.absent
|
|
"""
|
|
name = self.tmp_dir / "file_to_kill"
|
|
name.write_text("killme")
|
|
ret = self.run_state("file.absent", name=str(name))
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(name.is_file())
|
|
|
|
def test_absent_dir(self):
|
|
"""
|
|
file.absent
|
|
"""
|
|
name = self.tmp_dir / "dir_to_kill"
|
|
name.mkdir(exist_ok=True)
|
|
ret = self.run_state("file.absent", name=str(name))
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(name.is_dir())
|
|
|
|
def test_absent_link(self):
|
|
"""
|
|
file.absent
|
|
"""
|
|
name = self.tmp_dir / "link_to_kill"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
tgt = self.tmp_dir / "link_to_kill.tgt"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(tgt))
|
|
|
|
tgt.symlink_to(name, target_is_directory=IS_WINDOWS)
|
|
|
|
ret = self.run_state("file.absent", name=str(name))
|
|
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(name.exists())
|
|
self.assertFalse(name.is_symlink())
|
|
|
|
@with_tempfile()
|
|
def test_test_absent(self, name):
|
|
"""
|
|
file.absent test interface
|
|
"""
|
|
with salt.utils.files.fopen(name, "w+") as fp_:
|
|
fp_.write("killme")
|
|
ret = self.run_state("file.absent", test=True, name=name)
|
|
self.assertSaltNoneReturn(ret)
|
|
self.assertTrue(os.path.isfile(name))
|
|
|
|
def test_managed(self):
|
|
"""
|
|
file.managed
|
|
"""
|
|
name = self.tmp_dir / "grail_scene33"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
ret = self.run_state(
|
|
"file.managed", name=str(name), source="salt://grail/scene33"
|
|
)
|
|
src = pathlib.Path(RUNTIME_VARS.BASE_FILES) / "grail" / "scene33"
|
|
master_data = src.read_text()
|
|
minion_data = name.read_text()
|
|
self.assertEqual(master_data, minion_data)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
def test_managed_file_mode(self):
|
|
"""
|
|
file.managed, correct file permissions
|
|
"""
|
|
desired_mode = 504 # 0770 octal
|
|
name = self.tmp_dir / "grail_scene33"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
ret = self.run_state(
|
|
"file.managed", name=str(name), mode="0770", source="salt://grail/scene33"
|
|
)
|
|
|
|
if IS_WINDOWS:
|
|
expected = "The 'mode' option is not supported on Windows"
|
|
self.assertEqual(ret[list(ret)[0]]["comment"], expected)
|
|
self.assertSaltFalseReturn(ret)
|
|
return
|
|
|
|
resulting_mode = stat.S_IMODE(name.stat().st_mode)
|
|
self.assertEqual(oct(desired_mode), oct(resulting_mode))
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@skipIf(IS_WINDOWS, "Windows does not report any file modes. Skipping.")
|
|
def test_managed_file_mode_keep(self):
|
|
"""
|
|
Test using "mode: keep" in a file.managed state
|
|
"""
|
|
_test_managed_file_mode_keep_helper(self, local=False)
|
|
|
|
@skipIf(IS_WINDOWS, "Windows does not report any file modes. Skipping.")
|
|
def test_managed_file_mode_keep_local_source(self):
|
|
"""
|
|
Test using "mode: keep" in a file.managed state, with a local file path
|
|
as the source.
|
|
"""
|
|
_test_managed_file_mode_keep_helper(self, local=True)
|
|
|
|
def test_managed_file_mode_file_exists_replace(self):
|
|
"""
|
|
file.managed, existing file with replace=True, change permissions
|
|
"""
|
|
initial_mode = 504 # 0770 octal
|
|
desired_mode = 384 # 0600 octal
|
|
name = self.tmp_dir / "grail_scene33"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
mode=oct(initial_mode),
|
|
source="salt://grail/scene33",
|
|
)
|
|
|
|
if IS_WINDOWS:
|
|
expected = "The 'mode' option is not supported on Windows"
|
|
self.assertEqual(ret[list(ret)[0]]["comment"], expected)
|
|
self.assertSaltFalseReturn(ret)
|
|
return
|
|
|
|
resulting_mode = stat.S_IMODE(name.stat().st_mode)
|
|
self.assertEqual(oct(initial_mode), oct(resulting_mode))
|
|
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
replace=True,
|
|
mode=oct(desired_mode),
|
|
source="salt://grail/scene33",
|
|
)
|
|
resulting_mode = stat.S_IMODE(name.stat().st_mode)
|
|
self.assertEqual(oct(desired_mode), oct(resulting_mode))
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
def test_managed_file_mode_file_exists_noreplace(self):
|
|
"""
|
|
file.managed, existing file with replace=False, change permissions
|
|
"""
|
|
initial_mode = 504 # 0770 octal
|
|
desired_mode = 384 # 0600 octal
|
|
name = self.tmp_dir / "grail_scene33"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
replace=True,
|
|
mode=oct(initial_mode),
|
|
source="salt://grail/scene33",
|
|
)
|
|
|
|
if IS_WINDOWS:
|
|
expected = "The 'mode' option is not supported on Windows"
|
|
self.assertEqual(ret[list(ret)[0]]["comment"], expected)
|
|
self.assertSaltFalseReturn(ret)
|
|
return
|
|
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
replace=False,
|
|
mode=oct(desired_mode),
|
|
source="salt://grail/scene33",
|
|
)
|
|
resulting_mode = stat.S_IMODE(name.stat().st_mode)
|
|
self.assertEqual(oct(desired_mode), oct(resulting_mode))
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
def test_managed_file_with_grains_data(self):
|
|
"""
|
|
Test to ensure we can render grains data into a managed
|
|
file.
|
|
"""
|
|
grain_path = self.tmp_dir / "file-grain-test"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(grain_path))
|
|
state_file = "file-grainget"
|
|
|
|
self.run_function(
|
|
"state.sls", [state_file], pillar={"grain_path": str(grain_path)}
|
|
)
|
|
self.assertTrue(grain_path.exists())
|
|
|
|
file_contents = grain_path.read_text().splitlines(True)
|
|
|
|
match = "^minion\n"
|
|
self.assertTrue(re.match(match, file_contents[0]))
|
|
|
|
def test_managed_file_with_pillardefault_sls(self):
|
|
"""
|
|
Test to ensure when pillar data is not available
|
|
in sls file with pillar.get it uses the default
|
|
value.
|
|
"""
|
|
file_pillar_def = os.path.join(RUNTIME_VARS.TMP, "filepillar-defaultvalue")
|
|
self.addCleanup(self._delete_file, file_pillar_def)
|
|
state_name = "file-pillardefaultget"
|
|
|
|
log.warning("File Path: %s", file_pillar_def)
|
|
ret = self.run_function("state.sls", [state_name])
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
# Check to make sure the file was created
|
|
check_file = self.run_function("file.file_exists", [file_pillar_def])
|
|
self.assertTrue(check_file)
|
|
|
|
@pytest.mark.skip_if_not_root
|
|
def test_managed_dir_mode(self):
|
|
"""
|
|
Tests to ensure that file.managed creates directories with the
|
|
permissions requested with the dir_mode argument
|
|
"""
|
|
desired_mode = 511 # 0777 in octal
|
|
name = self.tmp_dir / "a" / "managed_dir_mode_test_file"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
desired_owner = "nobody"
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
source="salt://grail/scene33",
|
|
mode=600,
|
|
makedirs=True,
|
|
user=desired_owner,
|
|
dir_mode=oct(desired_mode), # 0777
|
|
)
|
|
if IS_WINDOWS:
|
|
expected = "The 'mode' option is not supported on Windows"
|
|
self.assertEqual(ret[list(ret)[0]]["comment"], expected)
|
|
self.assertSaltFalseReturn(ret)
|
|
return
|
|
|
|
resulting_mode = stat.S_IMODE(name.parent.stat().st_mode)
|
|
resulting_owner = pwd.getpwuid(name.parent.stat().st_uid).pw_name
|
|
self.assertEqual(oct(desired_mode), oct(resulting_mode))
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertEqual(desired_owner, resulting_owner)
|
|
|
|
def test_test_managed(self):
|
|
"""
|
|
file.managed test interface
|
|
"""
|
|
name = self.tmp_dir / "grail_not_not_scene33"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
ret = self.run_state(
|
|
"file.managed", test=True, name=str(name), source="salt://grail/scene33"
|
|
)
|
|
self.assertSaltNoneReturn(ret)
|
|
self.assertFalse(name.is_file())
|
|
|
|
def test_managed_show_changes_false(self):
|
|
"""
|
|
file.managed test interface
|
|
"""
|
|
name = self.tmp_dir / "grail_not_scene33"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
name.write_bytes(b"test_managed_show_changes_false\n")
|
|
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
source="salt://grail/scene33",
|
|
show_changes=False,
|
|
)
|
|
|
|
changes = next(iter(ret.values()))["changes"]
|
|
self.assertEqual("<show_changes=False>", changes["diff"])
|
|
|
|
def test_managed_show_changes_true(self):
|
|
"""
|
|
file.managed test interface
|
|
"""
|
|
name = self.tmp_dir / "grail_not_scene33"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
name.write_bytes(b"test_managed_show_changes_false\n")
|
|
|
|
ret = self.run_state(
|
|
"file.managed", name=str(name), source="salt://grail/scene33",
|
|
)
|
|
|
|
changes = next(iter(ret.values()))["changes"]
|
|
self.assertIn("diff", changes)
|
|
|
|
@skipIf(IS_WINDOWS, "Don't know how to fix for Windows")
|
|
def test_managed_escaped_file_path(self):
|
|
"""
|
|
file.managed test that 'salt://|' protects unusual characters in file path
|
|
"""
|
|
funny_file = salt.utils.files.mkstemp(
|
|
prefix="?f!le? n@=3&", suffix=".file type"
|
|
)
|
|
funny_file_name = os.path.split(funny_file)[1]
|
|
funny_url = "salt://|" + funny_file_name
|
|
funny_url_path = os.path.join(RUNTIME_VARS.BASE_FILES, funny_file_name)
|
|
|
|
state_name = "funny_file"
|
|
state_file_name = state_name + ".sls"
|
|
state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_file_name)
|
|
state_key = "file_|-{0}_|-{0}_|-managed".format(funny_file)
|
|
|
|
self.addCleanup(os.remove, state_file)
|
|
self.addCleanup(os.remove, funny_file)
|
|
self.addCleanup(os.remove, funny_url_path)
|
|
|
|
with salt.utils.files.fopen(funny_url_path, "w"):
|
|
pass
|
|
with salt.utils.files.fopen(state_file, "w") as fp_:
|
|
fp_.write(
|
|
textwrap.dedent(
|
|
"""\
|
|
{}:
|
|
file.managed:
|
|
- source: {}
|
|
- makedirs: True
|
|
""".format(
|
|
funny_file, funny_url
|
|
)
|
|
)
|
|
)
|
|
|
|
ret = self.run_function("state.sls", [state_name])
|
|
self.assertTrue(ret[state_key]["result"])
|
|
|
|
def test_managed_contents(self):
|
|
"""
|
|
test file.managed with contents that is a boolean, string, integer,
|
|
float, list, and dictionary
|
|
"""
|
|
state_name = "file-FileTest-test_managed_contents"
|
|
state_filename = state_name + ".sls"
|
|
state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
|
|
|
|
managed_files = {}
|
|
state_keys = {}
|
|
for typ in ("bool", "str", "int", "float", "list", "dict"):
|
|
managed_files[typ] = salt.utils.files.mkstemp()
|
|
state_keys[typ] = "file_|-{} file_|-{}_|-managed".format(
|
|
typ, managed_files[typ]
|
|
)
|
|
try:
|
|
with salt.utils.files.fopen(state_file, "w") as fd_:
|
|
fd_.write(
|
|
textwrap.dedent(
|
|
"""\
|
|
bool file:
|
|
file.managed:
|
|
- name: {bool}
|
|
- contents: True
|
|
|
|
str file:
|
|
file.managed:
|
|
- name: {str}
|
|
- contents: Salt was here.
|
|
|
|
int file:
|
|
file.managed:
|
|
- name: {int}
|
|
- contents: 340282366920938463463374607431768211456
|
|
|
|
float file:
|
|
file.managed:
|
|
- name: {float}
|
|
- contents: 1.7518e-45 # gravitational coupling constant
|
|
|
|
list file:
|
|
file.managed:
|
|
- name: {list}
|
|
- contents: [1, 1, 2, 3, 5, 8, 13]
|
|
|
|
dict file:
|
|
file.managed:
|
|
- name: {dict}
|
|
- contents:
|
|
C: charge
|
|
P: parity
|
|
T: time
|
|
""".format(
|
|
**managed_files
|
|
)
|
|
)
|
|
)
|
|
|
|
ret = self.run_function("state.sls", [state_name])
|
|
self.assertSaltTrueReturn(ret)
|
|
for typ in state_keys:
|
|
self.assertTrue(ret[state_keys[typ]]["result"])
|
|
self.assertIn("diff", ret[state_keys[typ]]["changes"])
|
|
finally:
|
|
if os.path.exists(state_file):
|
|
os.remove(state_file)
|
|
for typ in managed_files:
|
|
if os.path.exists(managed_files[typ]):
|
|
os.remove(managed_files[typ])
|
|
|
|
def test_onchanges_any_recursive_error_issues_50811(self):
|
|
"""
|
|
test that onchanges_any does not causes a recursive error
|
|
"""
|
|
state_name = "onchanges_any_recursive_error"
|
|
state_filename = state_name + ".sls"
|
|
state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
|
|
|
|
try:
|
|
with salt.utils.files.fopen(state_file, "w") as fd_:
|
|
fd_.write(
|
|
textwrap.dedent(
|
|
"""\
|
|
command-test:
|
|
cmd.run:
|
|
- name: ls
|
|
- onchanges_any:
|
|
- file: /tmp/an-unfollowed-file
|
|
"""
|
|
)
|
|
)
|
|
|
|
ret = self.run_function("state.sls", [state_name])
|
|
self.assertSaltFalseReturn(ret)
|
|
finally:
|
|
if os.path.exists(state_file):
|
|
os.remove(state_file)
|
|
|
|
def test_prerequired_issues_55775(self):
|
|
"""
|
|
Test that __prereqired__ is filter from file.replace
|
|
if __prereqired__ is not filter from file.replace an error will be raised
|
|
"""
|
|
state_name = "Test_Issues_55775"
|
|
state_filename = state_name + ".sls"
|
|
state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
|
|
test_file = os.path.join(RUNTIME_VARS.BASE_FILES, "Issues_55775.txt")
|
|
|
|
try:
|
|
with salt.utils.files.fopen(state_file, "w") as fd_:
|
|
fd_.write(
|
|
textwrap.dedent(
|
|
"""\
|
|
/tmp/bug.txt:
|
|
file.managed:
|
|
- name: {0}
|
|
- contents:
|
|
- foo
|
|
file.replace:
|
|
file.replace:
|
|
- name: {0}
|
|
- pattern: 'foo'
|
|
- repl: 'bar'
|
|
- prereq:
|
|
- test no changes
|
|
- test changes
|
|
test no changes:
|
|
test.succeed_without_changes:
|
|
- name: no changes
|
|
test changes:
|
|
test.succeed_with_changes:
|
|
- name: changes
|
|
- require:
|
|
- test: test no changes
|
|
""".format(
|
|
test_file
|
|
)
|
|
)
|
|
)
|
|
|
|
ret = self.run_function("state.sls", [state_name])
|
|
self.assertSaltTrueReturn(ret)
|
|
finally:
|
|
for fpath in (state_file, test_file):
|
|
if os.path.exists(fpath):
|
|
os.remove(fpath)
|
|
|
|
def test_managed_contents_with_contents_newline(self):
|
|
"""
|
|
test file.managed with contents by using the default content_newline
|
|
flag.
|
|
"""
|
|
contents = "test_managed_contents_with_newline_one"
|
|
name = self.tmp_dir / "foo"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
|
|
# Create a file named foo with contents as above but with a \n at EOF
|
|
self.run_state(
|
|
"file.managed", name=str(name), contents=contents, contents_newline=True
|
|
)
|
|
last_line = name.read_text()
|
|
self.assertEqual((contents + "\n"), last_line)
|
|
|
|
def test_managed_contents_with_contents_newline_false(self):
|
|
"""
|
|
test file.managed with contents by using the non default content_newline
|
|
flag.
|
|
"""
|
|
contents = "test_managed_contents_with_newline_one"
|
|
name = self.tmp_dir / "bar"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
|
|
# Create a file named foo with contents as above but with a \n at EOF
|
|
self.run_state(
|
|
"file.managed", name=str(name), contents=contents, contents_newline=False
|
|
)
|
|
last_line = name.read_text()
|
|
self.assertEqual(contents, last_line)
|
|
|
|
def test_managed_multiline_contents_with_contents_newline(self):
|
|
"""
|
|
test file.managed with contents by using the non default content_newline
|
|
flag.
|
|
"""
|
|
contents = "this is a cookie\nthis is another cookie"
|
|
name = self.tmp_dir / "bar"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
|
|
# Create a file named foo with contents as above but with a \n at EOF
|
|
self.run_state(
|
|
"file.managed", name=str(name), contents=contents, contents_newline=True
|
|
)
|
|
last_line = name.read_text()
|
|
self.assertEqual((contents + "\n"), last_line)
|
|
|
|
def test_managed_multiline_contents_with_contents_newline_false(self):
|
|
"""
|
|
test file.managed with contents by using the non default content_newline
|
|
flag.
|
|
"""
|
|
contents = "this is a cookie\nthis is another cookie"
|
|
name = self.tmp_dir / "bar"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
|
|
# Create a file named foo with contents as above but with a \n at EOF
|
|
self.run_state(
|
|
"file.managed", name=str(name), contents=contents, contents_newline=False
|
|
)
|
|
last_line = name.read_text()
|
|
self.assertEqual(contents, last_line)
|
|
|
|
@pytest.mark.skip_if_not_root
|
|
@skipIf(IS_WINDOWS, 'Windows does not support "mode" kwarg. Skipping.')
|
|
@skipIf(not salt.utils.path.which("visudo"), "sudo is missing")
|
|
def test_managed_check_cmd(self):
|
|
"""
|
|
Test file.managed passing a basic check_cmd kwarg. See Issue #38111.
|
|
"""
|
|
r_group = "root"
|
|
if salt.utils.platform.is_darwin() or salt.utils.platform.is_freebsd():
|
|
r_group = "wheel"
|
|
|
|
name = self.tmp_dir / "sudoers"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
user="root",
|
|
group=r_group,
|
|
mode=440,
|
|
check_cmd="visudo -c -s -f",
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertInSaltComment("Empty file", ret)
|
|
self.assertEqual(
|
|
ret["file_|-{0}_|-{0}_|-managed".format(name)]["changes"],
|
|
{"new": "file {} created".format(name), "mode": "0440"},
|
|
)
|
|
|
|
def test_managed_local_source_with_source_hash(self):
|
|
"""
|
|
Make sure that we enforce the source_hash even with local files
|
|
"""
|
|
name = self.tmp_dir / "local_source_with_source_hash"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
local_path = os.path.join(RUNTIME_VARS.BASE_FILES, "grail", "scene33")
|
|
actual_hash = "567fd840bf1548edc35c48eb66cdd78bfdfcccff"
|
|
if IS_WINDOWS:
|
|
# CRLF vs LF causes a different hash on windows
|
|
actual_hash = "f658a0ec121d9c17088795afcc6ff3c43cb9842a"
|
|
# Reverse the actual hash
|
|
bad_hash = actual_hash[::-1]
|
|
|
|
def remove_file():
|
|
try:
|
|
os.remove(str(name))
|
|
except OSError as exc:
|
|
if exc.errno != errno.ENOENT:
|
|
raise
|
|
|
|
def do_test(clean=False):
|
|
for proto in ("file://", ""):
|
|
source = proto + local_path
|
|
log.debug("Trying source %s", source)
|
|
try:
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
source=source,
|
|
source_hash="sha1={}".format(bad_hash),
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
# Shouldn't be any changes
|
|
self.assertFalse(ret["changes"])
|
|
# Check that we identified a hash mismatch
|
|
self.assertIn("does not match actual checksum", ret["comment"])
|
|
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
source=source,
|
|
source_hash="sha1={}".format(actual_hash),
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
finally:
|
|
if clean:
|
|
remove_file()
|
|
|
|
remove_file()
|
|
log.debug("Trying with nonexistant destination file")
|
|
do_test()
|
|
log.debug("Trying with destination file already present")
|
|
name.write_text("")
|
|
try:
|
|
do_test(clean=False)
|
|
finally:
|
|
remove_file()
|
|
|
|
def test_managed_local_source_does_not_exist(self):
|
|
"""
|
|
Make sure that we exit gracefully when a local source doesn't exist
|
|
"""
|
|
name = self.tmp_dir / "local_source_does_not_exist"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
local_path = os.path.join(RUNTIME_VARS.BASE_FILES, "grail", "scene99")
|
|
|
|
for proto in ("file://", ""):
|
|
source = proto + local_path
|
|
log.debug("Trying source %s", source)
|
|
ret = self.run_state("file.managed", name=str(name), source=source)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
# Shouldn't be any changes
|
|
self.assertFalse(ret["changes"])
|
|
# Check that we identified a hash mismatch
|
|
self.assertIn("does not exist", ret["comment"])
|
|
|
|
def test_managed_unicode_jinja_with_tojson_filter(self):
|
|
"""
|
|
Using {{ varname }} with a list or dictionary which contains unicode
|
|
types on Python 2 will result in Jinja rendering the "u" prefix on each
|
|
string. This tests that using the "tojson" jinja filter will dump them
|
|
to a format which can be successfully loaded by our YAML loader.
|
|
|
|
The two lines that should end up being rendered are meant to test two
|
|
issues that would trip up PyYAML if the "tojson" filter were not used:
|
|
|
|
1. A unicode string type would be loaded as a unicode literal with the
|
|
leading "u" as well as the quotes, rather than simply being loaded
|
|
as the proper unicode type which matches the content of the string
|
|
literal. In other words, u'foo' would be loaded literally as
|
|
u"u'foo'". This test includes actual non-ascii unicode in one of the
|
|
strings to confirm that this also handles these international
|
|
characters properly.
|
|
|
|
2. Any unicode string type (such as a URL) which contains a colon would
|
|
cause a ScannerError in PyYAML, as it would be assumed to delimit a
|
|
mapping node.
|
|
|
|
Dumping the data structure to JSON using the "tojson" jinja filter
|
|
should produce an inline data structure which is valid YAML and will be
|
|
loaded properly by our YAML loader.
|
|
"""
|
|
test_file = self.tmp_dir / "test-tojson.txt"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(test_file))
|
|
ret = self.run_function(
|
|
"state.apply", mods="tojson", pillar={"tojson-file": str(test_file)}
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"], ret
|
|
managed = salt.utils.stringutils.to_unicode(test_file.read_bytes())
|
|
expected = dedent(
|
|
"""\
|
|
Die Webseite ist https://saltstack.com.
|
|
Der Zucker ist süß.
|
|
|
|
"""
|
|
)
|
|
assert managed == expected, "{!r} != {!r}".format(managed, expected)
|
|
|
|
def test_managed_source_hash_indifferent_case(self):
|
|
"""
|
|
Test passing a source_hash as an uppercase hash.
|
|
|
|
This is a regression test for Issue #38914 and Issue #48230 (test=true use).
|
|
"""
|
|
name = self.tmp_dir / "source_hash_indifferent_case"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
state_name = "file_|-{0}_|" "-{0}_|-managed".format(name)
|
|
local_path = os.path.join(RUNTIME_VARS.BASE_FILES, "hello_world.txt")
|
|
actual_hash = "c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31"
|
|
if IS_WINDOWS:
|
|
# CRLF vs LF causes a differnt hash on windows
|
|
actual_hash = (
|
|
"92b772380a3f8e27a93e57e6deeca6c01da07f5aadce78bb2fbb20de10a66925"
|
|
)
|
|
uppercase_hash = actual_hash.upper()
|
|
|
|
# Lay down tmp file to test against
|
|
self.run_state(
|
|
"file.managed", name=str(name), source=local_path, source_hash=actual_hash
|
|
)
|
|
|
|
# Test uppercase source_hash: should return True with no changes
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
source=local_path,
|
|
source_hash=uppercase_hash,
|
|
)
|
|
assert ret[state_name]["result"] is True
|
|
assert ret[state_name]["changes"] == {}
|
|
|
|
# Test uppercase source_hash using test=true
|
|
# Should return True with no changes
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=str(name),
|
|
source=local_path,
|
|
source_hash=uppercase_hash,
|
|
test=True,
|
|
)
|
|
assert ret[state_name]["result"] is True
|
|
assert ret[state_name]["changes"] == {}
|
|
|
|
@with_tempfile(create=False)
|
|
def test_managed_latin1_diff(self, name):
|
|
"""
|
|
Tests that latin-1 file contents are represented properly in the diff
|
|
"""
|
|
# Lay down the initial file
|
|
ret = self.run_state(
|
|
"file.managed", name=name, source="salt://issue-48777/old.html"
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"] is True, ret
|
|
|
|
# Replace it with the new file and check the diff
|
|
ret = self.run_state(
|
|
"file.managed", name=name, source="salt://issue-48777/new.html"
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"] is True, ret
|
|
diff_lines = ret["changes"]["diff"].split(os.linesep)
|
|
assert "+räksmörgås" in diff_lines, diff_lines
|
|
|
|
@with_tempfile()
|
|
def test_managed_keep_source_false_salt(self, name):
|
|
"""
|
|
This test ensures that we properly clean the cached file if keep_source
|
|
is set to False, for source files using a salt:// URL
|
|
"""
|
|
source = "salt://grail/scene33"
|
|
saltenv = "base"
|
|
|
|
# Run the state
|
|
ret = self.run_state(
|
|
"file.managed", name=name, source=source, saltenv=saltenv, keep_source=False
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"] is True
|
|
|
|
# Now make sure that the file is not cached
|
|
result = self.run_function("cp.is_cached", [source, saltenv])
|
|
assert result == "", "File is still cached at {}".format(result)
|
|
|
|
@with_tempfile(create=False)
|
|
@with_tempfile(create=False)
|
|
def test_file_managed_onchanges(self, file1, file2):
|
|
"""
|
|
Test file.managed state with onchanges
|
|
"""
|
|
pillar = {
|
|
"file1": file1,
|
|
"file2": file2,
|
|
"source": "salt://testfile",
|
|
"req": "onchanges",
|
|
}
|
|
|
|
# Lay down the file used in the below SLS to ensure that when it is
|
|
# run, there are no changes.
|
|
self.run_state("file.managed", name=pillar["file2"], source=pillar["source"])
|
|
|
|
ret = self.repack_state_returns(
|
|
self.run_function(
|
|
"state.apply", mods="onchanges_prereq", pillar=pillar, test=True,
|
|
)
|
|
)
|
|
# The file states should both exit with None
|
|
assert ret["one"]["result"] is None, ret["one"]["result"]
|
|
assert ret["three"]["result"] is True, ret["three"]["result"]
|
|
# The first file state should have changes, since a new file was
|
|
# created. The other one should not, since we already created that file
|
|
# before applying the SLS file.
|
|
assert ret["one"]["changes"]
|
|
assert not ret["three"]["changes"], ret["three"]["changes"]
|
|
# The state watching 'one' should have been run due to changes
|
|
assert ret["two"]["comment"] == "Success!", ret["two"]["comment"]
|
|
# The state watching 'three' should not have been run
|
|
assert (
|
|
ret["four"]["comment"]
|
|
== "State was not run because none of the onchanges reqs changed"
|
|
), ret["four"]["comment"]
|
|
|
|
@with_tempfile(create=False)
|
|
@with_tempfile(create=False)
|
|
def test_file_managed_prereq(self, file1, file2):
|
|
"""
|
|
Test file.managed state with prereq
|
|
"""
|
|
pillar = {
|
|
"file1": file1,
|
|
"file2": file2,
|
|
"source": "salt://testfile",
|
|
"req": "prereq",
|
|
}
|
|
|
|
# Lay down the file used in the below SLS to ensure that when it is
|
|
# run, there are no changes.
|
|
self.run_state("file.managed", name=pillar["file2"], source=pillar["source"])
|
|
|
|
ret = self.repack_state_returns(
|
|
self.run_function(
|
|
"state.apply", mods="onchanges_prereq", pillar=pillar, test=True,
|
|
)
|
|
)
|
|
# The file states should both exit with None
|
|
assert ret["one"]["result"] is None, ret["one"]["result"]
|
|
assert ret["three"]["result"] is True, ret["three"]["result"]
|
|
# The first file state should have changes, since a new file was
|
|
# created. The other one should not, since we already created that file
|
|
# before applying the SLS file.
|
|
assert ret["one"]["changes"]
|
|
assert not ret["three"]["changes"], ret["three"]["changes"]
|
|
# The state watching 'one' should have been run due to changes
|
|
assert ret["two"]["comment"] == "Success!", ret["two"]["comment"]
|
|
# The state watching 'three' should not have been run
|
|
assert ret["four"]["comment"] == "No changes detected", ret["four"]["comment"]
|
|
|
|
def test_directory(self):
|
|
"""
|
|
file.directory
|
|
"""
|
|
name = self.tmp_dir / "a_new_dir"
|
|
self.addCleanup(salt.utils.files.rm_rf, str(name))
|
|
ret = self.run_state("file.directory", name=str(name))
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(name.is_dir())
|
|
|
|
def test_directory_symlink_dry_run(self):
|
|
"""
|
|
Ensure that symlinks are followed when file.directory is run with
|
|
test=True
|
|
"""
|
|
tmp_dir = self.tmp_dir / "pgdata"
|
|
self.addCleanup(salt.utils.files.rm_rf, str(tmp_dir))
|
|
sym_dir = self.tmp_dir / "pg_data"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(sym_dir))
|
|
|
|
tmp_dir.mkdir(0o0700)
|
|
sym_dir.symlink_to(tmp_dir, target_is_directory=IS_WINDOWS)
|
|
|
|
if IS_WINDOWS:
|
|
ret = self.run_state(
|
|
"file.directory",
|
|
test=True,
|
|
name=str(sym_dir),
|
|
follow_symlinks=True,
|
|
win_owner="Administrators",
|
|
)
|
|
else:
|
|
ret = self.run_state(
|
|
"file.directory",
|
|
test=True,
|
|
name=str(sym_dir),
|
|
follow_symlinks=True,
|
|
mode=700,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@requires_system_grains
|
|
@pytest.mark.skip_if_not_root
|
|
@skipIf(IS_WINDOWS, "Mode not available in Windows")
|
|
def test_directory_max_depth(self, grains):
|
|
"""
|
|
file.directory
|
|
Test the max_depth option by iteratively increasing the depth and
|
|
checking that no changes deeper than max_depth have been attempted
|
|
"""
|
|
|
|
def _get_oct_mode(name):
|
|
"""
|
|
Return a string octal representation of the permissions for name
|
|
"""
|
|
return salt.utils.files.normalize_mode(oct(os.stat(name).st_mode & 0o777))
|
|
|
|
top = os.path.join(RUNTIME_VARS.TMP, "top_dir")
|
|
self.addCleanup(salt.utils.files.rm_rf, top)
|
|
sub = os.path.join(top, "sub_dir")
|
|
subsub = os.path.join(sub, "sub_sub_dir")
|
|
dirs = [top, sub, subsub]
|
|
|
|
initial_mode = "0111"
|
|
changed_mode = "0555"
|
|
|
|
if grains["os_family"] in ("VMware Photon OS",):
|
|
initial_modes = {
|
|
0: {sub: "0750", subsub: "0110"},
|
|
1: {sub: "0110", subsub: "0110"},
|
|
2: {sub: "0110", subsub: "0110"},
|
|
}
|
|
else:
|
|
initial_modes = {
|
|
0: {sub: "0755", subsub: "0111"},
|
|
1: {sub: "0111", subsub: "0111"},
|
|
2: {sub: "0111", subsub: "0111"},
|
|
}
|
|
|
|
if not os.path.isdir(subsub):
|
|
os.makedirs(subsub, int(initial_mode, 8))
|
|
|
|
for depth in range(0, 3):
|
|
ret = self.run_state(
|
|
"file.directory",
|
|
name=top,
|
|
max_depth=depth,
|
|
dir_mode=changed_mode,
|
|
recurse=["mode"],
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
for changed_dir in dirs[0 : depth + 1]:
|
|
self.assertEqual(changed_mode, _get_oct_mode(changed_dir))
|
|
for untouched_dir in dirs[depth + 1 :]:
|
|
# Beginning in Python 3.7, os.makedirs no longer sets
|
|
# the mode of intermediate directories to the mode that
|
|
# is passed.
|
|
if sys.version_info >= (3, 7):
|
|
_mode = initial_modes[depth][untouched_dir]
|
|
self.assertEqual(_mode, _get_oct_mode(untouched_dir))
|
|
else:
|
|
self.assertEqual(initial_mode, _get_oct_mode(untouched_dir))
|
|
|
|
def test_test_directory(self):
|
|
"""
|
|
file.directory
|
|
"""
|
|
name = self.tmp_dir / "a_not_dir"
|
|
self.addCleanup(shutil.rmtree, str(name), ignore_errors=True)
|
|
ret = self.run_state("file.directory", test=True, name=str(name))
|
|
self.assertSaltNoneReturn(ret)
|
|
self.assertFalse(name.is_dir())
|
|
|
|
@with_tempdir()
|
|
def test_directory_clean(self, base_dir):
|
|
"""
|
|
file.directory with clean=True
|
|
"""
|
|
name = os.path.join(base_dir, "directory_clean_dir")
|
|
os.mkdir(name)
|
|
|
|
strayfile = os.path.join(name, "strayfile")
|
|
with salt.utils.files.fopen(strayfile, "w"):
|
|
pass
|
|
|
|
straydir = os.path.join(name, "straydir")
|
|
if not os.path.isdir(straydir):
|
|
os.makedirs(straydir)
|
|
|
|
with salt.utils.files.fopen(os.path.join(straydir, "strayfile2"), "w"):
|
|
pass
|
|
|
|
ret = self.run_state("file.directory", name=name, clean=True)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(os.path.exists(strayfile))
|
|
self.assertFalse(os.path.exists(straydir))
|
|
self.assertTrue(os.path.isdir(name))
|
|
|
|
def test_directory_is_idempotent(self):
|
|
"""
|
|
Ensure the file.directory state produces no changes when rerun.
|
|
"""
|
|
name = self.tmp_dir / "a_dir_twice"
|
|
self.addCleanup(salt.utils.files.rm_rf, str(name))
|
|
|
|
if IS_WINDOWS:
|
|
username = os.environ.get("USERNAME", "Administrators")
|
|
domain = os.environ.get("USERDOMAIN", "")
|
|
fullname = "{}\\{}".format(domain, username)
|
|
|
|
ret = self.run_state("file.directory", name=str(name), win_owner=fullname)
|
|
else:
|
|
ret = self.run_state("file.directory", name=str(name))
|
|
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
if IS_WINDOWS:
|
|
ret = self.run_state("file.directory", name=str(name), win_owner=username)
|
|
else:
|
|
ret = self.run_state("file.directory", name=str(name))
|
|
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertSaltStateChangesEqual(ret, {})
|
|
|
|
@with_tempdir()
|
|
def test_directory_clean_exclude(self, base_dir):
|
|
"""
|
|
file.directory with clean=True and exclude_pat set
|
|
"""
|
|
name = os.path.join(base_dir, "directory_clean_dir")
|
|
if not os.path.isdir(name):
|
|
os.makedirs(name)
|
|
|
|
strayfile = os.path.join(name, "strayfile")
|
|
with salt.utils.files.fopen(strayfile, "w"):
|
|
pass
|
|
|
|
straydir = os.path.join(name, "straydir")
|
|
if not os.path.isdir(straydir):
|
|
os.makedirs(straydir)
|
|
|
|
strayfile2 = os.path.join(straydir, "strayfile2")
|
|
with salt.utils.files.fopen(strayfile2, "w"):
|
|
pass
|
|
|
|
keepfile = os.path.join(straydir, "keepfile")
|
|
with salt.utils.files.fopen(keepfile, "w"):
|
|
pass
|
|
|
|
exclude_pat = "E@^straydir(|/keepfile)$"
|
|
if IS_WINDOWS:
|
|
exclude_pat = "E@^straydir(|\\\\keepfile)$"
|
|
|
|
ret = self.run_state(
|
|
"file.directory", name=name, clean=True, exclude_pat=exclude_pat
|
|
)
|
|
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(os.path.exists(strayfile))
|
|
self.assertFalse(os.path.exists(strayfile2))
|
|
self.assertTrue(os.path.exists(keepfile))
|
|
|
|
@skipIf(IS_WINDOWS, "Skip on windows")
|
|
@with_tempdir()
|
|
def test_test_directory_clean_exclude(self, base_dir):
|
|
"""
|
|
file.directory with test=True, clean=True and exclude_pat set
|
|
|
|
Skipped on windows because clean and exclude_pat not supported by
|
|
salt.sates.file._check_directory_win
|
|
"""
|
|
name = os.path.join(base_dir, "directory_clean_dir")
|
|
os.mkdir(name)
|
|
|
|
strayfile = os.path.join(name, "strayfile")
|
|
with salt.utils.files.fopen(strayfile, "w"):
|
|
pass
|
|
|
|
straydir = os.path.join(name, "straydir")
|
|
if not os.path.isdir(straydir):
|
|
os.makedirs(straydir)
|
|
|
|
strayfile2 = os.path.join(straydir, "strayfile2")
|
|
with salt.utils.files.fopen(strayfile2, "w"):
|
|
pass
|
|
|
|
keepfile = os.path.join(straydir, "keepfile")
|
|
with salt.utils.files.fopen(keepfile, "w"):
|
|
pass
|
|
|
|
exclude_pat = "E@^straydir(|/keepfile)$"
|
|
if IS_WINDOWS:
|
|
exclude_pat = "E@^straydir(|\\\\keepfile)$"
|
|
|
|
ret = self.run_state(
|
|
"file.directory", test=True, name=name, clean=True, exclude_pat=exclude_pat
|
|
)
|
|
|
|
comment = next(iter(ret.values()))["comment"]
|
|
|
|
self.assertSaltNoneReturn(ret)
|
|
self.assertTrue(os.path.exists(strayfile))
|
|
self.assertTrue(os.path.exists(strayfile2))
|
|
self.assertTrue(os.path.exists(keepfile))
|
|
|
|
self.assertIn(strayfile, comment)
|
|
self.assertIn(strayfile2, comment)
|
|
self.assertNotIn(keepfile, comment)
|
|
|
|
@with_tempdir()
|
|
def test_directory_clean_require_in(self, name):
|
|
"""
|
|
file.directory test with clean=True and require_in file
|
|
"""
|
|
state_name = "file-FileTest-test_directory_clean_require_in"
|
|
state_filename = state_name + ".sls"
|
|
state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
|
|
|
|
wrong_file = os.path.join(name, "wrong")
|
|
with salt.utils.files.fopen(wrong_file, "w") as fp:
|
|
fp.write("foo")
|
|
good_file = os.path.join(name, "bar")
|
|
|
|
with salt.utils.files.fopen(state_file, "w") as fp:
|
|
self.addCleanup(salt.utils.files.safe_rm, state_file)
|
|
fp.write(
|
|
textwrap.dedent(
|
|
"""\
|
|
some_dir:
|
|
file.directory:
|
|
- name: {name}
|
|
- clean: true
|
|
|
|
{good_file}:
|
|
file.managed:
|
|
- require_in:
|
|
- file: some_dir
|
|
""".format(
|
|
name=name, good_file=good_file
|
|
)
|
|
)
|
|
)
|
|
|
|
ret = self.run_function("state.sls", [state_name])
|
|
self.assertTrue(os.path.exists(good_file))
|
|
self.assertFalse(os.path.exists(wrong_file))
|
|
|
|
@with_tempdir()
|
|
def test_directory_clean_require_in_with_id(self, name):
|
|
"""
|
|
file.directory test with clean=True and require_in file with an ID
|
|
different from the file name
|
|
"""
|
|
state_name = "file-FileTest-test_directory_clean_require_in_with_id"
|
|
state_filename = state_name + ".sls"
|
|
state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
|
|
|
|
wrong_file = os.path.join(name, "wrong")
|
|
with salt.utils.files.fopen(wrong_file, "w") as fp:
|
|
fp.write("foo")
|
|
good_file = os.path.join(name, "bar")
|
|
|
|
with salt.utils.files.fopen(state_file, "w") as fp:
|
|
self.addCleanup(salt.utils.files.safe_rm, state_file)
|
|
fp.write(
|
|
textwrap.dedent(
|
|
"""\
|
|
some_dir:
|
|
file.directory:
|
|
- name: {name}
|
|
- clean: true
|
|
|
|
some_file:
|
|
file.managed:
|
|
- name: {good_file}
|
|
- require_in:
|
|
- file: some_dir
|
|
""".format(
|
|
name=name, good_file=good_file
|
|
)
|
|
)
|
|
)
|
|
|
|
ret = self.run_function("state.sls", [state_name])
|
|
self.assertTrue(os.path.exists(good_file))
|
|
self.assertFalse(os.path.exists(wrong_file))
|
|
|
|
@skipIf(
|
|
salt.utils.platform.is_darwin(),
|
|
"WAR ROOM TEMPORARY SKIP, Test is flaky on macosx",
|
|
)
|
|
@with_tempdir()
|
|
def test_directory_clean_require_with_name(self, name):
|
|
"""
|
|
file.directory test with clean=True and require with a file state
|
|
relatively to the state's name, not its ID.
|
|
"""
|
|
state_name = "file-FileTest-test_directory_clean_require_in_with_id"
|
|
state_filename = state_name + ".sls"
|
|
state_file = os.path.join(RUNTIME_VARS.BASE_FILES, state_filename)
|
|
|
|
wrong_file = os.path.join(name, "wrong")
|
|
with salt.utils.files.fopen(wrong_file, "w") as fp:
|
|
fp.write("foo")
|
|
good_file = os.path.join(name, "bar")
|
|
|
|
with salt.utils.files.fopen(state_file, "w") as fp:
|
|
self.addCleanup(salt.utils.files.safe_rm, state_file)
|
|
fp.write(
|
|
textwrap.dedent(
|
|
"""\
|
|
some_dir:
|
|
file.directory:
|
|
- name: {name}
|
|
- clean: true
|
|
- require:
|
|
# This requirement refers to the name of the following
|
|
# state, not its ID.
|
|
- file: {good_file}
|
|
|
|
some_file:
|
|
file.managed:
|
|
- name: {good_file}
|
|
""".format(
|
|
name=name, good_file=good_file
|
|
)
|
|
)
|
|
)
|
|
|
|
ret = self.run_function("state.sls", [state_name])
|
|
self.assertTrue(os.path.exists(good_file))
|
|
self.assertFalse(os.path.exists(wrong_file))
|
|
|
|
def test_directory_broken_symlink(self):
|
|
"""
|
|
Ensure that file.directory works even if a directory
|
|
contains broken symbolic link
|
|
"""
|
|
tmp_dir = self.tmp_dir / "foo"
|
|
tmp_dir.mkdir(0o700)
|
|
self.addCleanup(salt.utils.files.rm_rf, str(tmp_dir))
|
|
null_file = tmp_dir / "null"
|
|
broken_link = tmp_dir / "broken"
|
|
broken_link.symlink_to(null_file)
|
|
|
|
if IS_WINDOWS:
|
|
ret = self.run_state(
|
|
"file.directory",
|
|
name=str(tmp_dir),
|
|
recurse=["mode"],
|
|
follow_symlinks=True,
|
|
win_owner="Administrators",
|
|
)
|
|
else:
|
|
ret = self.run_state(
|
|
"file.directory",
|
|
name=str(tmp_dir),
|
|
recurse=["mode"],
|
|
file_mode=644,
|
|
dir_mode=755,
|
|
)
|
|
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempdir(create=False)
|
|
def test_recurse(self, name):
|
|
"""
|
|
file.recurse
|
|
"""
|
|
ret = self.run_state("file.recurse", name=name, source="salt://grail")
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(os.path.isfile(os.path.join(name, "36", "scene")))
|
|
|
|
@with_tempdir(create=False)
|
|
@with_tempdir(create=False)
|
|
def test_recurse_specific_env(self, dir1, dir2):
|
|
"""
|
|
file.recurse passing __env__
|
|
"""
|
|
ret = self.run_state(
|
|
"file.recurse", name=dir1, source="salt://holy", __env__="prod"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(os.path.isfile(os.path.join(dir1, "32", "scene")))
|
|
|
|
ret = self.run_state(
|
|
"file.recurse", name=dir2, source="salt://holy", saltenv="prod"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(os.path.isfile(os.path.join(dir2, "32", "scene")))
|
|
|
|
@with_tempdir(create=False)
|
|
@with_tempdir(create=False)
|
|
def test_recurse_specific_env_in_url(self, dir1, dir2):
|
|
"""
|
|
file.recurse passing __env__
|
|
"""
|
|
ret = self.run_state(
|
|
"file.recurse", name=dir1, source="salt://holy?saltenv=prod"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(os.path.isfile(os.path.join(dir1, "32", "scene")))
|
|
|
|
ret = self.run_state(
|
|
"file.recurse", name=dir2, source="salt://holy?saltenv=prod"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(os.path.isfile(os.path.join(dir2, "32", "scene")))
|
|
|
|
@with_tempdir(create=False)
|
|
def test_test_recurse(self, name):
|
|
"""
|
|
file.recurse test interface
|
|
"""
|
|
ret = self.run_state(
|
|
"file.recurse", test=True, name=name, source="salt://grail",
|
|
)
|
|
self.assertSaltNoneReturn(ret)
|
|
self.assertFalse(os.path.isfile(os.path.join(name, "36", "scene")))
|
|
self.assertFalse(os.path.exists(name))
|
|
|
|
@with_tempdir(create=False)
|
|
@with_tempdir(create=False)
|
|
def test_test_recurse_specific_env(self, dir1, dir2):
|
|
"""
|
|
file.recurse test interface
|
|
"""
|
|
ret = self.run_state(
|
|
"file.recurse", test=True, name=dir1, source="salt://holy", __env__="prod"
|
|
)
|
|
self.assertSaltNoneReturn(ret)
|
|
self.assertFalse(os.path.isfile(os.path.join(dir1, "32", "scene")))
|
|
self.assertFalse(os.path.exists(dir1))
|
|
|
|
ret = self.run_state(
|
|
"file.recurse", test=True, name=dir2, source="salt://holy", saltenv="prod"
|
|
)
|
|
self.assertSaltNoneReturn(ret)
|
|
self.assertFalse(os.path.isfile(os.path.join(dir2, "32", "scene")))
|
|
self.assertFalse(os.path.exists(dir2))
|
|
|
|
@with_tempdir(create=False)
|
|
def test_recurse_template(self, name):
|
|
"""
|
|
file.recurse with jinja template enabled
|
|
"""
|
|
_ts = "TEMPLATE TEST STRING"
|
|
ret = self.run_state(
|
|
"file.recurse",
|
|
name=name,
|
|
source="salt://grail",
|
|
template="jinja",
|
|
defaults={"spam": _ts},
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
with salt.utils.files.fopen(os.path.join(name, "scene33"), "r") as fp_:
|
|
contents = fp_.read()
|
|
self.assertIn(_ts, contents)
|
|
|
|
@with_tempdir()
|
|
def test_recurse_clean(self, name):
|
|
"""
|
|
file.recurse with clean=True
|
|
"""
|
|
strayfile = os.path.join(name, "strayfile")
|
|
with salt.utils.files.fopen(strayfile, "w"):
|
|
pass
|
|
|
|
# Corner cases: replacing file with a directory and vice versa
|
|
with salt.utils.files.fopen(os.path.join(name, "36"), "w"):
|
|
pass
|
|
os.makedirs(os.path.join(name, "scene33"))
|
|
ret = self.run_state(
|
|
"file.recurse", name=name, source="salt://grail", clean=True
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(os.path.exists(strayfile))
|
|
self.assertTrue(os.path.isfile(os.path.join(name, "36", "scene")))
|
|
self.assertTrue(os.path.isfile(os.path.join(name, "scene33")))
|
|
|
|
@with_tempdir()
|
|
def test_recurse_clean_specific_env(self, name):
|
|
"""
|
|
file.recurse with clean=True and __env__=prod
|
|
"""
|
|
strayfile = os.path.join(name, "strayfile")
|
|
with salt.utils.files.fopen(strayfile, "w"):
|
|
pass
|
|
|
|
# Corner cases: replacing file with a directory and vice versa
|
|
with salt.utils.files.fopen(os.path.join(name, "32"), "w"):
|
|
pass
|
|
os.makedirs(os.path.join(name, "scene34"))
|
|
ret = self.run_state(
|
|
"file.recurse", name=name, source="salt://holy", clean=True, __env__="prod"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(os.path.exists(strayfile))
|
|
self.assertTrue(os.path.isfile(os.path.join(name, "32", "scene")))
|
|
self.assertTrue(os.path.isfile(os.path.join(name, "scene34")))
|
|
|
|
@skipIf(IS_WINDOWS, "Skip on windows")
|
|
@with_tempdir()
|
|
def test_recurse_issue_34945(self, base_dir):
|
|
"""
|
|
This tests the case where the source dir for the file.recurse state
|
|
does not contain any files (only subdirectories), and the dir_mode is
|
|
being managed. For a long time, this corner case resulted in the top
|
|
level of the destination directory being created with the wrong initial
|
|
permissions, a problem that would be corrected later on in the
|
|
file.recurse state via running state.directory. However, the
|
|
file.directory state only gets called when there are files to be
|
|
managed in that directory, and when the source directory contains only
|
|
subdirectories, the incorrectly-set initial perms would not be
|
|
repaired.
|
|
|
|
This was fixed in https://github.com/saltstack/salt/pull/35309
|
|
|
|
Skipped on windows because dir_mode is not supported.
|
|
"""
|
|
dir_mode = "2775"
|
|
issue_dir = "issue-34945"
|
|
name = os.path.join(base_dir, issue_dir)
|
|
|
|
ret = self.run_state(
|
|
"file.recurse", name=name, source="salt://" + issue_dir, dir_mode=dir_mode
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
actual_dir_mode = oct(stat.S_IMODE(os.stat(name).st_mode))[-4:]
|
|
self.assertEqual(dir_mode, actual_dir_mode)
|
|
|
|
@with_tempdir(create=False)
|
|
def test_recurse_issue_40578(self, name):
|
|
"""
|
|
This ensures that the state doesn't raise an exception when it
|
|
encounters a file with a unicode filename in the process of invoking
|
|
file.source_list.
|
|
"""
|
|
ret = self.run_state("file.recurse", name=name, source="salt://соль")
|
|
self.assertSaltTrueReturn(ret)
|
|
files = salt.utils.data.decode(os.listdir(name), normalize=True)
|
|
self.assertEqual(
|
|
sorted(files), sorted(["foo.txt", "спам.txt", "яйца.txt"]),
|
|
)
|
|
|
|
@with_tempfile()
|
|
def test_replace(self, name):
|
|
"""
|
|
file.replace
|
|
"""
|
|
with salt.utils.files.fopen(name, "w+") as fp_:
|
|
fp_.write("change_me")
|
|
|
|
ret = self.run_state(
|
|
"file.replace", name=name, pattern="change", repl="salt", backup=False
|
|
)
|
|
|
|
with salt.utils.files.fopen(name, "r") as fp_:
|
|
self.assertIn("salt", fp_.read())
|
|
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempdir()
|
|
def test_replace_issue_18612(self, base_dir):
|
|
"""
|
|
Test the (mis-)behaviour of file.replace as described in #18612:
|
|
|
|
Using 'prepend_if_not_found' or 'append_if_not_found' resulted in
|
|
an infinitely growing file as 'file.replace' didn't check beforehand
|
|
whether the changes had already been done to the file
|
|
|
|
# Case description:
|
|
|
|
The tested file contains one commented line
|
|
The commented line should be uncommented in the end, nothing else should change
|
|
"""
|
|
test_name = "test_replace_issue_18612"
|
|
path_test = os.path.join(base_dir, test_name)
|
|
|
|
with salt.utils.files.fopen(path_test, "w+") as fp_test_:
|
|
fp_test_.write("# en_US.UTF-8")
|
|
|
|
ret = []
|
|
for x in range(0, 3):
|
|
ret.append(
|
|
self.run_state(
|
|
"file.replace",
|
|
name=path_test,
|
|
pattern="^# en_US.UTF-8$",
|
|
repl="en_US.UTF-8",
|
|
append_if_not_found=True,
|
|
)
|
|
)
|
|
|
|
# ensure, the number of lines didn't change, even after invoking 'file.replace' 3 times
|
|
with salt.utils.files.fopen(path_test, "r") as fp_test_:
|
|
self.assertTrue(sum(1 for _ in fp_test_) == 1)
|
|
|
|
# ensure, the replacement succeeded
|
|
with salt.utils.files.fopen(path_test, "r") as fp_test_:
|
|
self.assertTrue(fp_test_.read().startswith("en_US.UTF-8"))
|
|
|
|
# ensure, all runs of 'file.replace' reported success
|
|
for item in ret:
|
|
self.assertSaltTrueReturn(item)
|
|
|
|
@with_tempdir()
|
|
def test_replace_issue_18612_prepend(self, base_dir):
|
|
"""
|
|
Test the (mis-)behaviour of file.replace as described in #18612:
|
|
|
|
Using 'prepend_if_not_found' or 'append_if_not_found' resulted in
|
|
an infinitely growing file as 'file.replace' didn't check beforehand
|
|
whether the changes had already been done to the file
|
|
|
|
# Case description:
|
|
|
|
The tested multifile contains multiple lines not matching the pattern or replacement in any way
|
|
The replacement pattern should be prepended to the file
|
|
"""
|
|
test_name = "test_replace_issue_18612_prepend"
|
|
path_in = os.path.join(
|
|
RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
|
|
)
|
|
path_out = os.path.join(
|
|
RUNTIME_VARS.FILES, "file.replace", "{}.out".format(test_name)
|
|
)
|
|
path_test = os.path.join(base_dir, test_name)
|
|
|
|
# create test file based on initial template
|
|
shutil.copyfile(path_in, path_test)
|
|
|
|
ret = []
|
|
for x in range(0, 3):
|
|
ret.append(
|
|
self.run_state(
|
|
"file.replace",
|
|
name=path_test,
|
|
pattern="^# en_US.UTF-8$",
|
|
repl="en_US.UTF-8",
|
|
prepend_if_not_found=True,
|
|
)
|
|
)
|
|
|
|
# ensure, the resulting file contains the expected lines
|
|
self.assertTrue(filecmp.cmp(path_test, path_out))
|
|
|
|
# ensure the initial file was properly backed up
|
|
self.assertTrue(filecmp.cmp(path_test + ".bak", path_in))
|
|
|
|
# ensure, all runs of 'file.replace' reported success
|
|
for item in ret:
|
|
self.assertSaltTrueReturn(item)
|
|
|
|
@with_tempdir()
|
|
def test_replace_issue_18612_append(self, base_dir):
|
|
"""
|
|
Test the (mis-)behaviour of file.replace as described in #18612:
|
|
|
|
Using 'prepend_if_not_found' or 'append_if_not_found' resulted in
|
|
an infinitely growing file as 'file.replace' didn't check beforehand
|
|
whether the changes had already been done to the file
|
|
|
|
# Case description:
|
|
|
|
The tested multifile contains multiple lines not matching the pattern or replacement in any way
|
|
The replacement pattern should be appended to the file
|
|
"""
|
|
test_name = "test_replace_issue_18612_append"
|
|
path_in = os.path.join(
|
|
RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
|
|
)
|
|
path_out = os.path.join(
|
|
RUNTIME_VARS.FILES, "file.replace", "{}.out".format(test_name)
|
|
)
|
|
path_test = os.path.join(base_dir, test_name)
|
|
|
|
# create test file based on initial template
|
|
shutil.copyfile(path_in, path_test)
|
|
|
|
ret = []
|
|
for x in range(0, 3):
|
|
ret.append(
|
|
self.run_state(
|
|
"file.replace",
|
|
name=path_test,
|
|
pattern="^# en_US.UTF-8$",
|
|
repl="en_US.UTF-8",
|
|
append_if_not_found=True,
|
|
)
|
|
)
|
|
|
|
# ensure, the resulting file contains the expected lines
|
|
self.assertTrue(filecmp.cmp(path_test, path_out))
|
|
|
|
# ensure the initial file was properly backed up
|
|
self.assertTrue(filecmp.cmp(path_test + ".bak", path_in))
|
|
|
|
# ensure, all runs of 'file.replace' reported success
|
|
for item in ret:
|
|
self.assertSaltTrueReturn(item)
|
|
|
|
@with_tempdir()
|
|
def test_replace_issue_18612_append_not_found_content(self, base_dir):
|
|
"""
|
|
Test the (mis-)behaviour of file.replace as described in #18612:
|
|
|
|
Using 'prepend_if_not_found' or 'append_if_not_found' resulted in
|
|
an infinitely growing file as 'file.replace' didn't check beforehand
|
|
whether the changes had already been done to the file
|
|
|
|
# Case description:
|
|
|
|
The tested multifile contains multiple lines not matching the pattern or replacement in any way
|
|
The 'not_found_content' value should be appended to the file
|
|
"""
|
|
test_name = "test_replace_issue_18612_append_not_found_content"
|
|
path_in = os.path.join(
|
|
RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
|
|
)
|
|
path_out = os.path.join(
|
|
RUNTIME_VARS.FILES, "file.replace", "{}.out".format(test_name)
|
|
)
|
|
path_test = os.path.join(base_dir, test_name)
|
|
|
|
# create test file based on initial template
|
|
shutil.copyfile(path_in, path_test)
|
|
|
|
ret = []
|
|
for x in range(0, 3):
|
|
ret.append(
|
|
self.run_state(
|
|
"file.replace",
|
|
name=path_test,
|
|
pattern="^# en_US.UTF-8$",
|
|
repl="en_US.UTF-8",
|
|
append_if_not_found=True,
|
|
not_found_content="THIS LINE WASN'T FOUND! SO WE'RE APPENDING IT HERE!",
|
|
)
|
|
)
|
|
|
|
# ensure, the resulting file contains the expected lines
|
|
self.assertTrue(filecmp.cmp(path_test, path_out))
|
|
|
|
# ensure the initial file was properly backed up
|
|
self.assertTrue(filecmp.cmp(path_test + ".bak", path_in))
|
|
|
|
# ensure, all runs of 'file.replace' reported success
|
|
for item in ret:
|
|
self.assertSaltTrueReturn(item)
|
|
|
|
@with_tempdir()
|
|
def test_replace_issue_18612_change_mid_line_with_comment(self, base_dir):
|
|
"""
|
|
Test the (mis-)behaviour of file.replace as described in #18612:
|
|
|
|
Using 'prepend_if_not_found' or 'append_if_not_found' resulted in
|
|
an infinitely growing file as 'file.replace' didn't check beforehand
|
|
whether the changes had already been done to the file
|
|
|
|
# Case description:
|
|
|
|
The tested file contains 5 key=value pairs
|
|
The commented key=value pair #foo=bar should be changed to foo=salt
|
|
The comment char (#) in front of foo=bar should be removed
|
|
"""
|
|
test_name = "test_replace_issue_18612_change_mid_line_with_comment"
|
|
path_in = os.path.join(
|
|
RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
|
|
)
|
|
path_out = os.path.join(
|
|
RUNTIME_VARS.FILES, "file.replace", "{}.out".format(test_name)
|
|
)
|
|
path_test = os.path.join(base_dir, test_name)
|
|
|
|
# create test file based on initial template
|
|
shutil.copyfile(path_in, path_test)
|
|
|
|
ret = []
|
|
for x in range(0, 3):
|
|
ret.append(
|
|
self.run_state(
|
|
"file.replace",
|
|
name=path_test,
|
|
pattern="^#foo=bar($|(?=\r\n))",
|
|
repl="foo=salt",
|
|
append_if_not_found=True,
|
|
)
|
|
)
|
|
|
|
# ensure, the resulting file contains the expected lines
|
|
self.assertTrue(filecmp.cmp(path_test, path_out))
|
|
|
|
# ensure the initial file was properly backed up
|
|
self.assertTrue(filecmp.cmp(path_test + ".bak", path_in))
|
|
|
|
# ensure, all 'file.replace' runs reported success
|
|
for item in ret:
|
|
self.assertSaltTrueReturn(item)
|
|
|
|
@with_tempdir()
|
|
def test_replace_issue_18841_no_changes(self, base_dir):
|
|
"""
|
|
Test the (mis-)behaviour of file.replace as described in #18841:
|
|
|
|
Using file.replace in a way which shouldn't modify the file at all
|
|
results in changed mtime of the original file and a backup file being created.
|
|
|
|
# Case description
|
|
|
|
The tested file contains multiple lines
|
|
The tested file contains a line already matching the replacement (no change needed)
|
|
The tested file's content shouldn't change at all
|
|
The tested file's mtime shouldn't change at all
|
|
No backup file should be created
|
|
"""
|
|
test_name = "test_replace_issue_18841_no_changes"
|
|
path_in = os.path.join(
|
|
RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
|
|
)
|
|
path_test = os.path.join(base_dir, test_name)
|
|
|
|
# create test file based on initial template
|
|
shutil.copyfile(path_in, path_test)
|
|
|
|
# get (m|a)time of file
|
|
fstats_orig = os.stat(path_test)
|
|
|
|
# define how far we predate the file
|
|
age = 5 * 24 * 60 * 60
|
|
|
|
# set (m|a)time of file 5 days into the past
|
|
os.utime(path_test, (fstats_orig.st_mtime - age, fstats_orig.st_atime - age))
|
|
|
|
ret = self.run_state(
|
|
"file.replace",
|
|
name=path_test,
|
|
pattern="^hello world$",
|
|
repl="goodbye world",
|
|
show_changes=True,
|
|
flags=["IGNORECASE"],
|
|
backup=False,
|
|
)
|
|
|
|
# get (m|a)time of file
|
|
fstats_post = os.stat(path_test)
|
|
|
|
# ensure, the file content didn't change
|
|
self.assertTrue(filecmp.cmp(path_in, path_test))
|
|
|
|
# ensure no backup file was created
|
|
self.assertFalse(os.path.exists(path_test + ".bak"))
|
|
|
|
# ensure the file's mtime didn't change
|
|
self.assertTrue(fstats_post.st_mtime, fstats_orig.st_mtime - age)
|
|
|
|
# ensure, all 'file.replace' runs reported success
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
def test_serialize(self):
|
|
"""
|
|
Test to ensure that file.serialize returns a data structure that's
|
|
both serialized and formatted properly
|
|
"""
|
|
path_test = self.tmp_dir / "test_serialize"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(path_test))
|
|
ret = self.run_state(
|
|
"file.serialize",
|
|
name=str(path_test),
|
|
dataset={
|
|
"name": "naive",
|
|
"description": "A basic test",
|
|
"a_list": ["first_element", "second_element"],
|
|
"finally": "the last item",
|
|
},
|
|
formatter="json",
|
|
)
|
|
|
|
serialized_file = salt.utils.stringutils.to_unicode(path_test.read_bytes())
|
|
|
|
# The JSON serializer uses LF even on OSes where os.sep is CRLF.
|
|
expected_file = "\n".join(
|
|
[
|
|
"{",
|
|
' "a_list": [',
|
|
' "first_element",',
|
|
' "second_element"',
|
|
" ],",
|
|
' "description": "A basic test",',
|
|
' "finally": "the last item",',
|
|
' "name": "naive"',
|
|
"}",
|
|
"",
|
|
]
|
|
)
|
|
self.assertEqual(serialized_file, expected_file)
|
|
|
|
@with_tempfile(create=False)
|
|
def test_serializer_deserializer_opts(self, name):
|
|
"""
|
|
Test the serializer_opts and deserializer_opts options
|
|
"""
|
|
data1 = {"foo": {"bar": "%(x)s"}}
|
|
data2 = {"foo": {"abc": 123}}
|
|
merged = {"foo": {"y": "not_used", "x": "baz", "abc": 123, "bar": "baz"}}
|
|
|
|
ret = self.run_state(
|
|
"file.serialize",
|
|
name=name,
|
|
dataset=data1,
|
|
formatter="configparser",
|
|
deserializer_opts=[{"defaults": {"y": "not_used"}}],
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"], ret
|
|
# We should have warned about deserializer_opts being used when
|
|
# merge_if_exists was not set to True.
|
|
assert "warnings" in ret
|
|
|
|
# Run with merge_if_exists, as well as serializer and deserializer opts
|
|
# deserializer opts will be used for string interpolation of the %(x)s
|
|
# that was written to the file with data1 (i.e. bar should become baz)
|
|
ret = self.run_state(
|
|
"file.serialize",
|
|
name=name,
|
|
dataset=data2,
|
|
formatter="configparser",
|
|
merge_if_exists=True,
|
|
serializer_opts=[{"defaults": {"y": "not_used"}}],
|
|
deserializer_opts=[{"defaults": {"x": "baz"}}],
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"], ret
|
|
|
|
with salt.utils.files.fopen(name) as fp_:
|
|
serialized_data = salt.serializers.configparser.deserialize(fp_)
|
|
|
|
# If this test fails, this debug logging will help tell us how the
|
|
# serialized data differs from what was serialized.
|
|
log.debug("serialized_data = %r", serialized_data)
|
|
log.debug("merged = %r", merged)
|
|
# serializing with a default of 'y' will add y = not_used into foo
|
|
assert serialized_data["foo"]["y"] == merged["foo"]["y"]
|
|
# deserializing with default of x = baz will perform interpolation on %(x)s
|
|
# and bar will then = baz
|
|
assert serialized_data["foo"]["bar"] == merged["foo"]["bar"]
|
|
|
|
@with_tempfile(create=False)
|
|
def test_serializer_plist_binary_file_open(self, name):
|
|
"""
|
|
Test the serialization and deserialization of plists which should include
|
|
the "rb" file open arguments change specifically for this formatter to handle
|
|
binary plists.
|
|
"""
|
|
data1 = {"foo": {"bar": "%(x)s"}}
|
|
data2 = {"foo": {"abc": 123}}
|
|
merged = {"foo": {"abc": 123, "bar": "%(x)s"}}
|
|
|
|
ret = self.run_state(
|
|
"file.serialize",
|
|
name=name,
|
|
dataset=data1,
|
|
formatter="plist",
|
|
serializer_opts=[{"fmt": "FMT_BINARY"}],
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"], ret
|
|
|
|
# Run with merge_if_exists so we test the deserializer.
|
|
ret = self.run_state(
|
|
"file.serialize",
|
|
name=name,
|
|
dataset=data2,
|
|
formatter="plist",
|
|
merge_if_exists=True,
|
|
serializer_opts=[{"fmt": "FMT_BINARY"}],
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"], ret
|
|
|
|
with salt.utils.files.fopen(name, "rb") as fp_:
|
|
serialized_data = salt.serializers.plist.deserialize(fp_)
|
|
|
|
# make sure our serialized data matches what we expect
|
|
assert serialized_data["foo"] == merged["foo"]
|
|
|
|
@with_tempfile(create=False)
|
|
def test_serializer_plist_file_open(self, name):
|
|
"""
|
|
Test the serialization and deserialization of non binary plists with
|
|
the new line concatenation.
|
|
"""
|
|
data1 = {"foo": {"bar": "%(x)s"}}
|
|
data2 = {"foo": {"abc": 123}}
|
|
merged = {"foo": {"abc": 123, "bar": "%(x)s"}}
|
|
|
|
ret = self.run_state(
|
|
"file.serialize", name=name, dataset=data1, formatter="plist",
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"], ret
|
|
|
|
# Run with merge_if_exists so we test the deserializer.
|
|
ret = self.run_state(
|
|
"file.serialize",
|
|
name=name,
|
|
dataset=data2,
|
|
formatter="plist",
|
|
merge_if_exists=True,
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"], ret
|
|
|
|
with salt.utils.files.fopen(name, "rb") as fp_:
|
|
serialized_data = salt.serializers.plist.deserialize(fp_)
|
|
|
|
# make sure our serialized data matches what we expect
|
|
assert serialized_data["foo"] == merged["foo"]
|
|
|
|
@with_tempdir()
|
|
def test_replace_issue_18841_omit_backup(self, base_dir):
|
|
"""
|
|
Test the (mis-)behaviour of file.replace as described in #18841:
|
|
|
|
Using file.replace in a way which shouldn't modify the file at all
|
|
results in changed mtime of the original file and a backup file being created.
|
|
|
|
# Case description
|
|
|
|
The tested file contains multiple lines
|
|
The tested file contains a line already matching the replacement (no change needed)
|
|
The tested file's content shouldn't change at all
|
|
The tested file's mtime shouldn't change at all
|
|
No backup file should be created, although backup=False isn't explicitly defined
|
|
"""
|
|
test_name = "test_replace_issue_18841_omit_backup"
|
|
path_in = os.path.join(
|
|
RUNTIME_VARS.FILES, "file.replace", "{}.in".format(test_name)
|
|
)
|
|
path_test = os.path.join(base_dir, test_name)
|
|
|
|
# create test file based on initial template
|
|
shutil.copyfile(path_in, path_test)
|
|
|
|
# get (m|a)time of file
|
|
fstats_orig = os.stat(path_test)
|
|
|
|
# define how far we predate the file
|
|
age = 5 * 24 * 60 * 60
|
|
|
|
# set (m|a)time of file 5 days into the past
|
|
os.utime(path_test, (fstats_orig.st_mtime - age, fstats_orig.st_atime - age))
|
|
|
|
ret = self.run_state(
|
|
"file.replace",
|
|
name=path_test,
|
|
pattern="^hello world$",
|
|
repl="goodbye world",
|
|
show_changes=True,
|
|
flags=["IGNORECASE"],
|
|
)
|
|
|
|
# get (m|a)time of file
|
|
fstats_post = os.stat(path_test)
|
|
|
|
# ensure, the file content didn't change
|
|
self.assertTrue(filecmp.cmp(path_in, path_test))
|
|
|
|
# ensure no backup file was created
|
|
self.assertFalse(os.path.exists(path_test + ".bak"))
|
|
|
|
# ensure the file's mtime didn't change
|
|
self.assertTrue(fstats_post.st_mtime, fstats_orig.st_mtime - age)
|
|
|
|
# ensure, all 'file.replace' runs reported success
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempfile()
|
|
def test_comment(self, name):
|
|
"""
|
|
file.comment
|
|
"""
|
|
# write a line to file
|
|
with salt.utils.files.fopen(name, "w+") as fp_:
|
|
fp_.write("comment_me")
|
|
|
|
# Look for changes with test=True: return should be "None" at the first run
|
|
ret = self.run_state("file.comment", test=True, name=name, regex="^comment")
|
|
self.assertSaltNoneReturn(ret)
|
|
|
|
# comment once
|
|
ret = self.run_state("file.comment", name=name, regex="^comment")
|
|
# result is positive
|
|
self.assertSaltTrueReturn(ret)
|
|
# line is commented
|
|
with salt.utils.files.fopen(name, "r") as fp_:
|
|
self.assertTrue(fp_.read().startswith("#comment"))
|
|
|
|
# comment twice
|
|
ret = self.run_state("file.comment", name=name, regex="^comment")
|
|
|
|
# result is still positive
|
|
self.assertSaltTrueReturn(ret)
|
|
# line is still commented
|
|
with salt.utils.files.fopen(name, "r") as fp_:
|
|
self.assertTrue(fp_.read().startswith("#comment"))
|
|
|
|
# Test previously commented file returns "True" now and not "None" with test=True
|
|
ret = self.run_state("file.comment", test=True, name=name, regex="^comment")
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempfile()
|
|
def test_test_comment(self, name):
|
|
"""
|
|
file.comment test interface
|
|
"""
|
|
with salt.utils.files.fopen(name, "w+") as fp_:
|
|
fp_.write("comment_me")
|
|
ret = self.run_state("file.comment", test=True, name=name, regex=".*comment.*",)
|
|
with salt.utils.files.fopen(name, "r") as fp_:
|
|
self.assertNotIn("#comment", fp_.read())
|
|
self.assertSaltNoneReturn(ret)
|
|
|
|
@with_tempfile()
|
|
def test_uncomment(self, name):
|
|
"""
|
|
file.uncomment
|
|
"""
|
|
with salt.utils.files.fopen(name, "w+") as fp_:
|
|
fp_.write("#comment_me")
|
|
ret = self.run_state("file.uncomment", name=name, regex="^comment")
|
|
with salt.utils.files.fopen(name, "r") as fp_:
|
|
self.assertNotIn("#comment", fp_.read())
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempfile()
|
|
def test_test_uncomment(self, name):
|
|
"""
|
|
file.comment test interface
|
|
"""
|
|
with salt.utils.files.fopen(name, "w+") as fp_:
|
|
fp_.write("#comment_me")
|
|
ret = self.run_state("file.uncomment", test=True, name=name, regex="^comment.*")
|
|
with salt.utils.files.fopen(name, "r") as fp_:
|
|
self.assertIn("#comment", fp_.read())
|
|
self.assertSaltNoneReturn(ret)
|
|
|
|
@with_tempfile()
|
|
def test_append(self, name):
|
|
"""
|
|
file.append
|
|
"""
|
|
with salt.utils.files.fopen(name, "w+") as fp_:
|
|
fp_.write("#salty!")
|
|
ret = self.run_state("file.append", name=name, text="cheese")
|
|
with salt.utils.files.fopen(name, "r") as fp_:
|
|
self.assertIn("cheese", fp_.read())
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempfile()
|
|
def test_test_append(self, name):
|
|
"""
|
|
file.append test interface
|
|
"""
|
|
with salt.utils.files.fopen(name, "w+") as fp_:
|
|
fp_.write("#salty!")
|
|
ret = self.run_state("file.append", test=True, name=name, text="cheese")
|
|
with salt.utils.files.fopen(name, "r") as fp_:
|
|
self.assertNotIn("cheese", fp_.read())
|
|
self.assertSaltNoneReturn(ret)
|
|
|
|
@with_tempdir()
|
|
def test_append_issue_1864_makedirs(self, base_dir):
|
|
"""
|
|
file.append but create directories if needed as an option, and create
|
|
the file if it doesn't exist
|
|
"""
|
|
fname = "append_issue_1864_makedirs"
|
|
name = os.path.join(base_dir, fname)
|
|
|
|
# Non existing file get's touched
|
|
ret = self.run_state("file.append", name=name, text="cheese", makedirs=True)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
# Nested directory and file get's touched
|
|
name = os.path.join(base_dir, "issue_1864", fname)
|
|
ret = self.run_state("file.append", name=name, text="cheese", makedirs=True)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
# Parent directory exists but file does not and makedirs is False
|
|
name = os.path.join(base_dir, "issue_1864", fname + "2")
|
|
ret = self.run_state("file.append", name=name, text="cheese")
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(os.path.isfile(name))
|
|
|
|
@with_tempdir()
|
|
def test_prepend_issue_27401_makedirs(self, base_dir):
|
|
"""
|
|
file.prepend but create directories if needed as an option, and create
|
|
the file if it doesn't exist
|
|
"""
|
|
fname = "prepend_issue_27401"
|
|
name = os.path.join(base_dir, fname)
|
|
|
|
# Non existing file get's touched
|
|
ret = self.run_state("file.prepend", name=name, text="cheese", makedirs=True)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
# Nested directory and file get's touched
|
|
name = os.path.join(base_dir, "issue_27401", fname)
|
|
ret = self.run_state("file.prepend", name=name, text="cheese", makedirs=True)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
# Parent directory exists but file does not and makedirs is False
|
|
name = os.path.join(base_dir, "issue_27401", fname + "2")
|
|
ret = self.run_state("file.prepend", name=name, text="cheese")
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(os.path.isfile(name))
|
|
|
|
@with_tempfile()
|
|
def test_touch(self, name):
|
|
"""
|
|
file.touch
|
|
"""
|
|
ret = self.run_state("file.touch", name=name)
|
|
self.assertTrue(os.path.isfile(name))
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempfile(create=False)
|
|
def test_test_touch(self, name):
|
|
"""
|
|
file.touch test interface
|
|
"""
|
|
ret = self.run_state("file.touch", test=True, name=name)
|
|
self.assertFalse(os.path.isfile(name))
|
|
self.assertSaltNoneReturn(ret)
|
|
|
|
@with_tempdir()
|
|
def test_touch_directory(self, base_dir):
|
|
"""
|
|
file.touch a directory
|
|
"""
|
|
name = os.path.join(base_dir, "touch_test_dir")
|
|
os.mkdir(name)
|
|
|
|
ret = self.run_state("file.touch", name=name)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(os.path.isdir(name))
|
|
|
|
@with_tempdir()
|
|
def test_issue_2227_file_append(self, base_dir):
|
|
"""
|
|
Text to append includes a percent symbol
|
|
"""
|
|
# let's make use of existing state to create a file with contents to
|
|
# test against
|
|
tmp_file_append = os.path.join(base_dir, "test.append")
|
|
|
|
self.run_state("file.touch", name=tmp_file_append)
|
|
self.run_state(
|
|
"file.append", name=tmp_file_append, source="salt://testappend/firstif"
|
|
)
|
|
self.run_state(
|
|
"file.append", name=tmp_file_append, source="salt://testappend/secondif"
|
|
)
|
|
|
|
# Now our real test
|
|
try:
|
|
ret = self.run_state(
|
|
"file.append", name=tmp_file_append, text="HISTTIMEFORMAT='%F %T '"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
with salt.utils.files.fopen(tmp_file_append, "r") as fp_:
|
|
contents_pre = fp_.read()
|
|
|
|
# It should not append text again
|
|
ret = self.run_state(
|
|
"file.append", name=tmp_file_append, text="HISTTIMEFORMAT='%F %T '"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
with salt.utils.files.fopen(tmp_file_append, "r") as fp_:
|
|
contents_post = fp_.read()
|
|
|
|
self.assertEqual(contents_pre, contents_post)
|
|
except AssertionError:
|
|
if os.path.exists(tmp_file_append):
|
|
shutil.copy(tmp_file_append, tmp_file_append + ".bak")
|
|
raise
|
|
|
|
@with_tempdir()
|
|
def test_issue_2401_file_comment(self, base_dir):
|
|
# Get a path to the temporary file
|
|
tmp_file = os.path.join(base_dir, "issue-2041-comment.txt")
|
|
# Write some data to it
|
|
with salt.utils.files.fopen(tmp_file, "w") as fp_:
|
|
fp_.write("hello\nworld\n")
|
|
# create the sls template
|
|
template_lines = [
|
|
"{}:".format(tmp_file),
|
|
" file.comment:",
|
|
" - regex: ^world",
|
|
]
|
|
template = "\n".join(template_lines)
|
|
try:
|
|
ret = self.run_function("state.template_str", [template], timeout=120)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertNotInSaltComment("Pattern already commented", ret)
|
|
self.assertInSaltComment("Commented lines successfully", ret)
|
|
|
|
# This next time, it is already commented.
|
|
ret = self.run_function("state.template_str", [template], timeout=120)
|
|
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertInSaltComment("Pattern already commented", ret)
|
|
except AssertionError:
|
|
shutil.copy(tmp_file, tmp_file + ".bak")
|
|
raise
|
|
|
|
@with_tempdir()
|
|
def test_issue_2379_file_append(self, base_dir):
|
|
# Get a path to the temporary file
|
|
tmp_file = os.path.join(base_dir, "issue-2379-file-append.txt")
|
|
# Write some data to it
|
|
with salt.utils.files.fopen(tmp_file, "w") as fp_:
|
|
fp_.write(
|
|
"hello\nworld\n" # Some junk
|
|
"#PermitRootLogin yes\n" # Commented text
|
|
"# PermitRootLogin yes\n" # Commented text with space
|
|
)
|
|
# create the sls template
|
|
template_lines = [
|
|
"{}:".format(tmp_file),
|
|
" file.append:",
|
|
" - text: PermitRootLogin yes",
|
|
]
|
|
template = "\n".join(template_lines)
|
|
try:
|
|
ret = self.run_function("state.template_str", [template])
|
|
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertInSaltComment("Appended 1 lines", ret)
|
|
except AssertionError:
|
|
shutil.copy(tmp_file, tmp_file + ".bak")
|
|
raise
|
|
|
|
@skipIf(IS_WINDOWS, "Mode not available in Windows")
|
|
@with_tempdir(create=False)
|
|
@with_tempdir(create=False)
|
|
def test_issue_2726_mode_kwarg(self, dir1, dir2):
|
|
# Let's test for the wrong usage approach
|
|
bad_mode_kwarg_testfile = os.path.join(dir1, "bad_mode_kwarg", "testfile")
|
|
bad_template = [
|
|
"{}:".format(bad_mode_kwarg_testfile),
|
|
" file.recurse:",
|
|
" - source: salt://testfile",
|
|
" - mode: 644",
|
|
]
|
|
ret = self.run_function("state.template_str", [os.linesep.join(bad_template)])
|
|
self.assertSaltFalseReturn(ret)
|
|
self.assertInSaltComment(
|
|
"'mode' is not allowed in 'file.recurse'. Please use "
|
|
"'file_mode' and 'dir_mode'.",
|
|
ret,
|
|
)
|
|
self.assertNotInSaltComment(
|
|
"TypeError: managed() got multiple values for keyword " "argument 'mode'",
|
|
ret,
|
|
)
|
|
|
|
# Now, the correct usage approach
|
|
good_mode_kwargs_testfile = os.path.join(dir2, "good_mode_kwargs", "testappend")
|
|
good_template = [
|
|
"{}:".format(good_mode_kwargs_testfile),
|
|
" file.recurse:",
|
|
" - source: salt://testappend",
|
|
" - dir_mode: 744",
|
|
" - file_mode: 644",
|
|
]
|
|
ret = self.run_function("state.template_str", [os.linesep.join(good_template)])
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempdir()
|
|
def test_issue_8343_accumulated_require_in(self, base_dir):
|
|
template_path = os.path.join(RUNTIME_VARS.TMP_STATE_TREE, "issue-8343.sls")
|
|
testcase_filedest = os.path.join(base_dir, "issue-8343.txt")
|
|
if os.path.exists(template_path):
|
|
os.remove(template_path)
|
|
if os.path.exists(testcase_filedest):
|
|
os.remove(testcase_filedest)
|
|
sls_template = [
|
|
"{0}:",
|
|
" file.managed:",
|
|
" - contents: |",
|
|
" #",
|
|
"",
|
|
"prepend-foo-accumulator-from-pillar:",
|
|
" file.accumulated:",
|
|
" - require_in:",
|
|
" - file: prepend-foo-management",
|
|
" - filename: {0}",
|
|
" - text: |",
|
|
" foo",
|
|
"",
|
|
"append-foo-accumulator-from-pillar:",
|
|
" file.accumulated:",
|
|
" - require_in:",
|
|
" - file: append-foo-management",
|
|
" - filename: {0}",
|
|
" - text: |",
|
|
" bar",
|
|
"",
|
|
"prepend-foo-management:",
|
|
" file.blockreplace:",
|
|
" - name: {0}",
|
|
' - marker_start: "#-- start salt managed zonestart -- PLEASE, DO NOT EDIT"',
|
|
' - marker_end: "#-- end salt managed zonestart --"',
|
|
" - content: ''",
|
|
" - prepend_if_not_found: True",
|
|
" - backup: '.bak'",
|
|
" - show_changes: True",
|
|
"",
|
|
"append-foo-management:",
|
|
" file.blockreplace:",
|
|
" - name: {0}",
|
|
' - marker_start: "#-- start salt managed zoneend -- PLEASE, DO NOT EDIT"',
|
|
' - marker_end: "#-- end salt managed zoneend --"',
|
|
" - content: ''",
|
|
" - append_if_not_found: True",
|
|
" - backup: '.bak2'",
|
|
" - show_changes: True",
|
|
"",
|
|
]
|
|
with salt.utils.files.fopen(template_path, "w") as fp_:
|
|
fp_.write(os.linesep.join(sls_template).format(testcase_filedest))
|
|
|
|
ret = self.run_function("state.sls", mods="issue-8343")
|
|
for name, step in ret.items():
|
|
self.assertSaltTrueReturn({name: step})
|
|
with salt.utils.files.fopen(testcase_filedest) as fp_:
|
|
contents = fp_.read().split(os.linesep)
|
|
|
|
expected = [
|
|
"#-- start salt managed zonestart -- PLEASE, DO NOT EDIT",
|
|
"foo",
|
|
"#-- end salt managed zonestart --",
|
|
"#",
|
|
"#-- start salt managed zoneend -- PLEASE, DO NOT EDIT",
|
|
"bar",
|
|
"#-- end salt managed zoneend --",
|
|
"",
|
|
]
|
|
|
|
self.assertEqual(
|
|
[salt.utils.stringutils.to_str(line) for line in expected], contents
|
|
)
|
|
|
|
@with_tempdir()
|
|
def test_issue_11003_immutable_lazy_proxy_sum(self, base_dir):
|
|
# causes the Import-Module ServerManager error on Windows
|
|
template_path = os.path.join(RUNTIME_VARS.TMP_STATE_TREE, "issue-11003.sls")
|
|
testcase_filedest = os.path.join(base_dir, "issue-11003.txt")
|
|
sls_template = [
|
|
"a{0}:",
|
|
" file.absent:",
|
|
" - name: {0}",
|
|
"",
|
|
"{0}:",
|
|
" file.managed:",
|
|
" - contents: |",
|
|
" #",
|
|
"",
|
|
"test-acc1:",
|
|
" file.accumulated:",
|
|
" - require_in:",
|
|
" - file: final",
|
|
" - filename: {0}",
|
|
" - text: |",
|
|
" bar",
|
|
"",
|
|
"test-acc2:",
|
|
" file.accumulated:",
|
|
" - watch_in:",
|
|
" - file: final",
|
|
" - filename: {0}",
|
|
" - text: |",
|
|
" baz",
|
|
"",
|
|
"final:",
|
|
" file.blockreplace:",
|
|
" - name: {0}",
|
|
' - marker_start: "#-- start managed zone PLEASE, DO NOT EDIT"',
|
|
' - marker_end: "#-- end managed zone"',
|
|
" - content: ''",
|
|
" - append_if_not_found: True",
|
|
" - show_changes: True",
|
|
]
|
|
|
|
with salt.utils.files.fopen(template_path, "w") as fp_:
|
|
fp_.write(os.linesep.join(sls_template).format(testcase_filedest))
|
|
|
|
ret = self.run_function("state.sls", mods="issue-11003", timeout=600)
|
|
for name, step in ret.items():
|
|
self.assertSaltTrueReturn({name: step})
|
|
with salt.utils.files.fopen(testcase_filedest) as fp_:
|
|
contents = fp_.read().split(os.linesep)
|
|
|
|
begin = contents.index("#-- start managed zone PLEASE, DO NOT EDIT") + 1
|
|
end = contents.index("#-- end managed zone")
|
|
block_contents = contents[begin:end]
|
|
for item in ("", "bar", "baz"):
|
|
block_contents.remove(item)
|
|
self.assertEqual(block_contents, [])
|
|
|
|
@with_tempdir()
|
|
def test_issue_8947_utf8_sls(self, base_dir):
|
|
"""
|
|
Test some file operation with utf-8 characters on the sls
|
|
|
|
This is more generic than just a file test. Feel free to move
|
|
"""
|
|
self.maxDiff = None
|
|
korean_1 = "한국어 시험"
|
|
korean_2 = "첫 번째 행"
|
|
korean_3 = "마지막 행"
|
|
test_file = os.path.join(base_dir, "{}.txt".format(korean_1))
|
|
test_file_encoded = test_file
|
|
template_path = os.path.join(RUNTIME_VARS.TMP_STATE_TREE, "issue-8947.sls")
|
|
# create the sls template
|
|
template = textwrap.dedent(
|
|
"""\
|
|
some-utf8-file-create:
|
|
file.managed:
|
|
- name: {test_file}
|
|
- contents: {korean_1}
|
|
- makedirs: True
|
|
- replace: True
|
|
- show_diff: True
|
|
some-utf8-file-create2:
|
|
file.managed:
|
|
- name: {test_file}
|
|
- contents: |
|
|
{korean_2}
|
|
{korean_1}
|
|
{korean_3}
|
|
- replace: True
|
|
- show_diff: True
|
|
""".format(
|
|
**locals()
|
|
)
|
|
)
|
|
|
|
if not salt.utils.platform.is_windows():
|
|
template += textwrap.dedent(
|
|
"""\
|
|
some-utf8-file-content-test:
|
|
cmd.run:
|
|
- name: 'cat "{test_file}"'
|
|
- require:
|
|
- file: some-utf8-file-create2
|
|
""".format(
|
|
**locals()
|
|
)
|
|
)
|
|
|
|
# Save template file
|
|
with salt.utils.files.fopen(template_path, "wb") as fp_:
|
|
fp_.write(salt.utils.stringutils.to_bytes(template))
|
|
|
|
try:
|
|
result = self.run_function("state.sls", mods="issue-8947")
|
|
if not isinstance(result, dict):
|
|
raise AssertionError(
|
|
"Something went really wrong while testing this sls: {!r}".format(
|
|
result
|
|
)
|
|
)
|
|
diff = "--- \n+++ \n@@ -1 +1,3 @@\n"
|
|
diff += "+첫 번째 행{0}" " 한국어 시험{0}" "+마지막 행{0}".format(os.linesep)
|
|
|
|
ret = {x.split("_|-")[1]: y for x, y in result.items()}
|
|
|
|
# Confirm initial creation of file
|
|
self.assertEqual(
|
|
ret["some-utf8-file-create"]["comment"],
|
|
"File {} updated".format(test_file_encoded),
|
|
)
|
|
self.assertEqual(
|
|
ret["some-utf8-file-create"]["changes"], {"diff": "New file"}
|
|
)
|
|
|
|
# Confirm file was modified and that the diff was as expected
|
|
self.assertEqual(
|
|
ret["some-utf8-file-create2"]["comment"],
|
|
"File {} updated".format(test_file_encoded),
|
|
)
|
|
self.assertEqual(ret["some-utf8-file-create2"]["changes"], {"diff": diff})
|
|
if salt.utils.platform.is_windows():
|
|
import subprocess
|
|
import win32api
|
|
|
|
proc = subprocess.run(
|
|
["type", win32api.GetShortPathName(test_file)],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
check=True,
|
|
# type is an shell internal command
|
|
shell=True, # nosec
|
|
)
|
|
self.assertEqual(
|
|
proc.stdout.decode("utf-8"),
|
|
os.linesep.join((korean_2, korean_1, korean_3)) + os.linesep,
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
ret["some-utf8-file-content-test"]["comment"],
|
|
'Command "cat "{}"" run'.format(test_file_encoded),
|
|
)
|
|
self.assertEqual(
|
|
ret["some-utf8-file-content-test"]["changes"]["stdout"],
|
|
"\n".join((korean_2, korean_1, korean_3)),
|
|
)
|
|
finally:
|
|
try:
|
|
os.remove(template_path)
|
|
except OSError:
|
|
pass
|
|
|
|
@pytest.mark.skip_if_not_root
|
|
@skipIf(not HAS_PWD, "pwd not available. Skipping test")
|
|
@skipIf(not HAS_GRP, "grp not available. Skipping test")
|
|
@with_system_user_and_group(
|
|
TEST_SYSTEM_USER, TEST_SYSTEM_GROUP, on_existing="delete", delete=True
|
|
)
|
|
@with_tempdir()
|
|
@skipIf(salt.utils.platform.is_freebsd(), "Test is failing on FreeBSD")
|
|
def test_issue_12209_follow_symlinks(self, tempdir, user, group):
|
|
"""
|
|
Ensure that symlinks are properly chowned when recursing (following
|
|
symlinks)
|
|
"""
|
|
# Make the directories for this test
|
|
onedir = os.path.join(tempdir, "one")
|
|
twodir = os.path.join(tempdir, "two")
|
|
os.mkdir(onedir)
|
|
os.symlink(onedir, twodir)
|
|
|
|
# Run the state
|
|
ret = self.run_state(
|
|
"file.directory",
|
|
name=tempdir,
|
|
follow_symlinks=True,
|
|
user=user,
|
|
group=group,
|
|
recurse=["user", "group"],
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
# Double-check, in case state mis-reported a True result. Since we are
|
|
# following symlinks, we expect twodir to still be owned by root, but
|
|
# onedir should be owned by the 'issue12209' user.
|
|
onestats = os.stat(onedir)
|
|
twostats = os.lstat(twodir)
|
|
self.assertEqual(pwd.getpwuid(onestats.st_uid).pw_name, user)
|
|
self.assertEqual(pwd.getpwuid(twostats.st_uid).pw_name, "root")
|
|
self.assertEqual(grp.getgrgid(onestats.st_gid).gr_name, group)
|
|
if salt.utils.path.which("id"):
|
|
root_group = self.run_function("user.primary_group", ["root"])
|
|
self.assertEqual(grp.getgrgid(twostats.st_gid).gr_name, root_group)
|
|
|
|
@pytest.mark.skip_if_not_root
|
|
@skipIf(not HAS_PWD, "pwd not available. Skipping test")
|
|
@skipIf(not HAS_GRP, "grp not available. Skipping test")
|
|
@with_system_user_and_group(
|
|
TEST_SYSTEM_USER, TEST_SYSTEM_GROUP, on_existing="delete", delete=True
|
|
)
|
|
@with_tempdir()
|
|
def test_issue_12209_no_follow_symlinks(self, tempdir, user, group):
|
|
"""
|
|
Ensure that symlinks are properly chowned when recursing (not following
|
|
symlinks)
|
|
"""
|
|
# Make the directories for this test
|
|
onedir = os.path.join(tempdir, "one")
|
|
twodir = os.path.join(tempdir, "two")
|
|
os.mkdir(onedir)
|
|
os.symlink(onedir, twodir)
|
|
|
|
# Run the state
|
|
ret = self.run_state(
|
|
"file.directory",
|
|
name=tempdir,
|
|
follow_symlinks=False,
|
|
user=user,
|
|
group=group,
|
|
recurse=["user", "group"],
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
# Double-check, in case state mis-reported a True result. Since we
|
|
# are not following symlinks, we expect twodir to now be owned by
|
|
# the 'issue12209' user, just link onedir.
|
|
onestats = os.stat(onedir)
|
|
twostats = os.lstat(twodir)
|
|
self.assertEqual(pwd.getpwuid(onestats.st_uid).pw_name, user)
|
|
self.assertEqual(pwd.getpwuid(twostats.st_uid).pw_name, user)
|
|
self.assertEqual(grp.getgrgid(onestats.st_gid).gr_name, group)
|
|
self.assertEqual(grp.getgrgid(twostats.st_gid).gr_name, group)
|
|
|
|
@with_tempfile(create=False)
|
|
@with_tempfile()
|
|
def test_template_local_file(self, source, dest):
|
|
"""
|
|
Test a file.managed state with a local file as the source. Test both
|
|
with the file:// protocol designation prepended, and without it.
|
|
"""
|
|
with salt.utils.files.fopen(source, "w") as fp_:
|
|
fp_.write("{{ foo }}\n")
|
|
|
|
for prefix in ("file://", ""):
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=dest,
|
|
source=prefix + source,
|
|
template="jinja",
|
|
context={"foo": "Hello world!"},
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempfile()
|
|
def test_template_local_file_noclobber(self, source):
|
|
"""
|
|
Test the case where a source file is in the minion's local filesystem,
|
|
and the source path is the same as the destination path.
|
|
"""
|
|
with salt.utils.files.fopen(source, "w") as fp_:
|
|
fp_.write("{{ foo }}\n")
|
|
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=source,
|
|
source=source,
|
|
template="jinja",
|
|
context={"foo": "Hello world!"},
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
self.assertIn(
|
|
("Source file cannot be the same as destination"),
|
|
ret[next(iter(ret))]["comment"],
|
|
)
|
|
|
|
@with_tempfile(create=False)
|
|
@with_tempfile(create=False)
|
|
def test_issue_25250_force_copy_deletes(self, source, dest):
|
|
"""
|
|
ensure force option in copy state does not delete target file
|
|
"""
|
|
shutil.copyfile(os.path.join(RUNTIME_VARS.FILES, "hosts"), source)
|
|
shutil.copyfile(os.path.join(RUNTIME_VARS.FILES, "file/base/cheese"), dest)
|
|
|
|
self.run_state("file.copy", name=dest, source=source, force=True)
|
|
self.assertTrue(os.path.exists(dest))
|
|
self.assertTrue(filecmp.cmp(source, dest))
|
|
|
|
os.remove(source)
|
|
os.remove(dest)
|
|
|
|
@pytest.mark.destructive_test
|
|
@pytest.mark.skip_if_not_root
|
|
@skipIf(IS_WINDOWS, "Windows does not report any file modes. Skipping.")
|
|
@with_tempfile()
|
|
def test_file_copy_make_dirs(self, source):
|
|
"""
|
|
ensure make_dirs creates correct user perms
|
|
"""
|
|
shutil.copyfile(os.path.join(RUNTIME_VARS.FILES, "hosts"), source)
|
|
dest = self.tmp_dir / "dir1" / "dir2" / "copied_file.txt"
|
|
self.addCleanup(salt.utils.files.rm_rf, str(dest.parent.parent))
|
|
|
|
user = "salt"
|
|
mode = "0644"
|
|
ret = self.run_function("user.add", [user])
|
|
self.assertTrue(ret, "Failed to add user. Are you running as sudo?")
|
|
ret = self.run_state(
|
|
"file.copy",
|
|
name=str(dest),
|
|
source=source,
|
|
user=user,
|
|
makedirs=True,
|
|
mode=mode,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
file_checks = [str(dest), str(dest.parent), str(dest.parent.parent)]
|
|
for check in file_checks:
|
|
user_check = self.run_function("file.get_user", [check])
|
|
mode_check = self.run_function("file.get_mode", [check])
|
|
self.assertEqual(user_check, user)
|
|
self.assertEqual(salt.utils.files.normalize_mode(mode_check), mode)
|
|
|
|
@pytest.mark.skip_if_not_root
|
|
@skipIf(not HAS_PWD, "pwd not available. Skipping test")
|
|
@skipIf(not HAS_GRP, "grp not available. Skipping test")
|
|
@with_system_user_and_group(
|
|
TEST_SYSTEM_USER, TEST_SYSTEM_GROUP, on_existing="delete", delete=True
|
|
)
|
|
def test_owner_after_setuid(self, user, group):
|
|
|
|
"""
|
|
Test to check file user/group after setting setuid or setgid.
|
|
Because Python os.chown() does reset the setuid/setgid to 0.
|
|
https://github.com/saltstack/salt/pull/45257
|
|
"""
|
|
|
|
# Desired configuration.
|
|
desired_file = self.tmp_dir / "file_with_setuid"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(desired_file))
|
|
desired = {
|
|
"file": str(desired_file),
|
|
"user": user,
|
|
"group": group,
|
|
"mode": "4750",
|
|
}
|
|
|
|
# Run the state.
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=desired["file"],
|
|
user=desired["user"],
|
|
group=desired["group"],
|
|
mode=desired["mode"],
|
|
)
|
|
|
|
# Check result.
|
|
file_stat = desired_file.stat()
|
|
result = {
|
|
"user": pwd.getpwuid(file_stat.st_uid).pw_name,
|
|
"group": grp.getgrgid(file_stat.st_gid).gr_name,
|
|
"mode": oct(stat.S_IMODE(file_stat.st_mode)),
|
|
}
|
|
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertEqual(desired["user"], result["user"])
|
|
self.assertEqual(desired["group"], result["group"])
|
|
self.assertEqual(desired["mode"], result["mode"].lstrip("0Oo"))
|
|
|
|
def test_binary_contents(self):
|
|
"""
|
|
This tests to ensure that binary contents do not cause a traceback.
|
|
"""
|
|
name = self.tmp_dir / "1px.gif"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
ret = self.run_state("file.managed", name=str(name), contents=BINARY_FILE)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
def test_binary_contents_twice(self):
|
|
"""
|
|
This test ensures that after a binary file is created, salt can confirm
|
|
that the file is in the correct state.
|
|
"""
|
|
# Create a binary file
|
|
name = self.tmp_dir / "1px.gif"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
|
|
# First run state ensures file is created
|
|
ret = self.run_state("file.managed", name=str(name), contents=BINARY_FILE)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
# Second run of state ensures file is in correct state
|
|
ret = self.run_state("file.managed", name=str(name), contents=BINARY_FILE)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@pytest.mark.skip_if_not_root
|
|
@skipIf(not HAS_PWD, "pwd not available. Skipping test")
|
|
@skipIf(not HAS_GRP, "grp not available. Skipping test")
|
|
@with_system_user_and_group(
|
|
TEST_SYSTEM_USER, TEST_SYSTEM_GROUP, on_existing="delete", delete=True
|
|
)
|
|
@with_tempdir()
|
|
def test_issue_48336_file_managed_mode_setuid(self, tempdir, user, group):
|
|
"""
|
|
Ensure that mode is correct with changing of ownership and group
|
|
symlinks)
|
|
"""
|
|
tempfile = os.path.join(tempdir, "temp_file_issue_48336")
|
|
|
|
# Run the state
|
|
ret = self.run_state(
|
|
"file.managed", name=tempfile, user=user, group=group, mode="4750",
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
# Check that the owner and group are correct, and
|
|
# the mode is what we expect
|
|
temp_file_stats = os.stat(tempfile)
|
|
|
|
# Normalize the mode
|
|
temp_file_mode = str(oct(stat.S_IMODE(temp_file_stats.st_mode)))
|
|
temp_file_mode = salt.utils.files.normalize_mode(temp_file_mode)
|
|
|
|
self.assertEqual(temp_file_mode, "4750")
|
|
self.assertEqual(pwd.getpwuid(temp_file_stats.st_uid).pw_name, user)
|
|
self.assertEqual(grp.getgrgid(temp_file_stats.st_gid).gr_name, group)
|
|
|
|
@with_tempdir()
|
|
def test_issue_48557(self, tempdir):
|
|
tempfile = os.path.join(tempdir, "temp_file_issue_48557")
|
|
with salt.utils.files.fopen(tempfile, "wb") as fp:
|
|
fp.write(os.linesep.join(["test1", "test2", "test3", ""]).encode("utf-8"))
|
|
ret = self.run_state(
|
|
"file.line", name=tempfile, after="test2", mode="insert", content="test4"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
with salt.utils.files.fopen(tempfile, "rb") as fp:
|
|
content = fp.read()
|
|
self.assertEqual(
|
|
content,
|
|
os.linesep.join(["test1", "test2", "test4", "test3", ""]).encode("utf-8"),
|
|
)
|
|
|
|
def test_managed_file_issue_51208(self):
|
|
"""
|
|
Test to ensure we can handle a file with escaped double-quotes
|
|
"""
|
|
name = self.tmp_dir / "issue_51208.txt"
|
|
self.addCleanup(salt.utils.files.safe_rm, str(name))
|
|
ret = self.run_state(
|
|
"file.managed", name=str(name), source="salt://issue-51208/vimrc.stub"
|
|
)
|
|
src = pathlib.Path(RUNTIME_VARS.BASE_FILES) / "issue-51208" / "vimrc.stub"
|
|
master_data = src.read_text()
|
|
minion_data = name.read_text()
|
|
self.assertEqual(master_data, minion_data)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempfile()
|
|
def test_keyvalue(self, name):
|
|
"""
|
|
file.keyvalue
|
|
"""
|
|
content = dedent(
|
|
"""\
|
|
# This is the sshd server system-wide configuration file. See
|
|
# sshd_config(5) for more information.
|
|
|
|
# The strategy used for options in the default sshd_config shipped with
|
|
# OpenSSH is to specify options with their default value where
|
|
# possible, but leave them commented. Uncommented options override the
|
|
# default value.
|
|
|
|
#Port 22
|
|
#AddressFamily any
|
|
#ListenAddress 0.0.0.0
|
|
#ListenAddress ::
|
|
|
|
#HostKey /etc/ssh/ssh_host_rsa_key
|
|
#HostKey /etc/ssh/ssh_host_ecdsa_key
|
|
#HostKey /etc/ssh/ssh_host_ed25519_key
|
|
|
|
# Ciphers and keying
|
|
#RekeyLimit default none
|
|
|
|
# Logging
|
|
#SyslogFacility AUTH
|
|
#LogLevel INFO
|
|
|
|
# Authentication:
|
|
|
|
#LoginGraceTime 2m
|
|
#PermitRootLogin prohibit-password
|
|
#StrictModes yes
|
|
#MaxAuthTries 6
|
|
#MaxSessions 10
|
|
"""
|
|
)
|
|
|
|
with salt.utils.files.fopen(name, "w+") as fp_:
|
|
fp_.write(content)
|
|
|
|
ret = self.run_state(
|
|
"file.keyvalue",
|
|
name=name,
|
|
key="permitrootlogin",
|
|
value="no",
|
|
separator=" ",
|
|
uncomment=" #",
|
|
key_ignore_case=True,
|
|
)
|
|
|
|
with salt.utils.files.fopen(name, "r") as fp_:
|
|
file_contents = fp_.read()
|
|
self.assertNotIn("#PermitRootLogin", file_contents)
|
|
self.assertNotIn("prohibit-password", file_contents)
|
|
self.assertIn("PermitRootLogin no", file_contents)
|
|
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
@with_tempdir()
|
|
@pytest.mark.slow_test
|
|
def test_issue_1896_file_append_source(self, base_dir):
|
|
"""
|
|
Verify that we can append a file's contents
|
|
"""
|
|
testfile = os.path.join(base_dir, "test.append")
|
|
|
|
ret = self.run_state("file.touch", name=testfile)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = self.run_state(
|
|
"file.append", name=testfile, source="salt://testappend/firstif"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = self.run_state(
|
|
"file.append", name=testfile, source="salt://testappend/secondif"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
with salt.utils.files.fopen(testfile, "r") as fp_:
|
|
testfile_contents = salt.utils.stringutils.to_unicode(fp_.read())
|
|
|
|
contents = textwrap.dedent(
|
|
"""\
|
|
# set variable identifying the chroot you work in (used in the prompt below)
|
|
if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then
|
|
debian_chroot=$(cat /etc/debian_chroot)
|
|
fi
|
|
|
|
# enable bash completion in interactive shells
|
|
if [ -f /etc/bash_completion ] && ! shopt -oq posix; then
|
|
. /etc/bash_completion
|
|
fi
|
|
"""
|
|
)
|
|
|
|
if salt.utils.platform.is_windows():
|
|
new_contents = contents.splitlines()
|
|
contents = os.linesep.join(new_contents)
|
|
contents += os.linesep
|
|
|
|
self.assertMultiLineEqual(contents, testfile_contents)
|
|
|
|
ret = self.run_state(
|
|
"file.append", name=testfile, source="salt://testappend/secondif"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = self.run_state(
|
|
"file.append", name=testfile, source="salt://testappend/firstif"
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
with salt.utils.files.fopen(testfile, "r") as fp_:
|
|
testfile_contents = salt.utils.stringutils.to_unicode(fp_.read())
|
|
|
|
self.assertMultiLineEqual(contents, testfile_contents)
|
|
|
|
|
|
@pytest.mark.windows_whitelisted
|
|
class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
|
|
marker_start = "# start"
|
|
marker_end = "# end"
|
|
content = dedent(
|
|
"""\
|
|
Line 1 of block
|
|
Line 2 of block
|
|
"""
|
|
)
|
|
without_block = dedent(
|
|
"""\
|
|
Hello world!
|
|
|
|
# comment here
|
|
"""
|
|
)
|
|
with_non_matching_block = dedent(
|
|
"""\
|
|
Hello world!
|
|
|
|
# start
|
|
No match here
|
|
# end
|
|
# comment here
|
|
"""
|
|
)
|
|
with_non_matching_block_and_marker_end_not_after_newline = dedent(
|
|
"""\
|
|
Hello world!
|
|
|
|
# start
|
|
No match here# end
|
|
# comment here
|
|
"""
|
|
)
|
|
with_matching_block = dedent(
|
|
"""\
|
|
Hello world!
|
|
|
|
# start
|
|
Line 1 of block
|
|
Line 2 of block
|
|
# end
|
|
# comment here
|
|
"""
|
|
)
|
|
with_matching_block_and_extra_newline = dedent(
|
|
"""\
|
|
Hello world!
|
|
|
|
# start
|
|
Line 1 of block
|
|
Line 2 of block
|
|
|
|
# end
|
|
# comment here
|
|
"""
|
|
)
|
|
with_matching_block_and_marker_end_not_after_newline = dedent(
|
|
"""\
|
|
Hello world!
|
|
|
|
# start
|
|
Line 1 of block
|
|
Line 2 of block# end
|
|
# comment here
|
|
"""
|
|
)
|
|
content_explicit_posix_newlines = "Line 1 of block\n" "Line 2 of block\n"
|
|
content_explicit_windows_newlines = "Line 1 of block\r\n" "Line 2 of block\r\n"
|
|
without_block_explicit_posix_newlines = "Hello world!\n\n" "# comment here\n"
|
|
without_block_explicit_windows_newlines = (
|
|
"Hello world!\r\n\r\n" "# comment here\r\n"
|
|
)
|
|
with_block_prepended_explicit_posix_newlines = (
|
|
"# start\n"
|
|
"Line 1 of block\n"
|
|
"Line 2 of block\n"
|
|
"# end\n"
|
|
"Hello world!\n\n"
|
|
"# comment here\n"
|
|
)
|
|
with_block_prepended_explicit_windows_newlines = (
|
|
"# start\r\n"
|
|
"Line 1 of block\r\n"
|
|
"Line 2 of block\r\n"
|
|
"# end\r\n"
|
|
"Hello world!\r\n\r\n"
|
|
"# comment here\r\n"
|
|
)
|
|
with_block_appended_explicit_posix_newlines = (
|
|
"Hello world!\n\n"
|
|
"# comment here\n"
|
|
"# start\n"
|
|
"Line 1 of block\n"
|
|
"Line 2 of block\n"
|
|
"# end\n"
|
|
)
|
|
with_block_appended_explicit_windows_newlines = (
|
|
"Hello world!\r\n\r\n"
|
|
"# comment here\r\n"
|
|
"# start\r\n"
|
|
"Line 1 of block\r\n"
|
|
"Line 2 of block\r\n"
|
|
"# end\r\n"
|
|
)
|
|
|
|
@staticmethod
|
|
def _write(dest, content):
|
|
with salt.utils.files.fopen(dest, "wb") as fp_:
|
|
fp_.write(salt.utils.stringutils.to_bytes(content))
|
|
|
|
@staticmethod
|
|
def _read(src):
|
|
with salt.utils.files.fopen(src, "rb") as fp_:
|
|
return salt.utils.stringutils.to_unicode(fp_.read())
|
|
|
|
@with_tempfile()
|
|
def test_prepend(self, name):
|
|
"""
|
|
Test blockreplace when prepend_if_not_found=True and block doesn't
|
|
exist in file.
|
|
"""
|
|
expected = (
|
|
self.marker_start
|
|
+ os.linesep
|
|
+ self.content
|
|
+ self.marker_end
|
|
+ os.linesep
|
|
+ self.without_block
|
|
)
|
|
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
@with_tempfile()
|
|
def test_prepend_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when prepend_if_not_found=True and block doesn't
|
|
exist in file. Test with append_newline explicitly set to True.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
expected = (
|
|
self.marker_start
|
|
+ os.linesep
|
|
+ self.content
|
|
+ os.linesep
|
|
+ self.marker_end
|
|
+ os.linesep
|
|
+ self.without_block
|
|
)
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
# Pass 2: content does not end in newline
|
|
expected = (
|
|
self.marker_start
|
|
+ os.linesep
|
|
+ self.content
|
|
+ self.marker_end
|
|
+ os.linesep
|
|
+ self.without_block
|
|
)
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
@with_tempfile()
|
|
def test_prepend_no_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when prepend_if_not_found=True and block doesn't
|
|
exist in file. Test with append_newline explicitly set to False.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
expected = (
|
|
self.marker_start
|
|
+ os.linesep
|
|
+ self.content
|
|
+ self.marker_end
|
|
+ os.linesep
|
|
+ self.without_block
|
|
)
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
# Pass 2: content does not end in newline
|
|
expected = (
|
|
self.marker_start
|
|
+ os.linesep
|
|
+ self.content.rstrip("\r\n")
|
|
+ self.marker_end
|
|
+ os.linesep
|
|
+ self.without_block
|
|
)
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
@with_tempfile()
|
|
def test_append(self, name):
|
|
"""
|
|
Test blockreplace when append_if_not_found=True and block doesn't
|
|
exist in file.
|
|
"""
|
|
expected = (
|
|
self.without_block
|
|
+ self.marker_start
|
|
+ os.linesep
|
|
+ self.content
|
|
+ self.marker_end
|
|
+ os.linesep
|
|
)
|
|
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
@with_tempfile()
|
|
def test_append_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when append_if_not_found=True and block doesn't
|
|
exist in file. Test with append_newline explicitly set to True.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
expected = (
|
|
self.without_block
|
|
+ self.marker_start
|
|
+ os.linesep
|
|
+ self.content
|
|
+ os.linesep
|
|
+ self.marker_end
|
|
+ os.linesep
|
|
)
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
# Pass 2: content does not end in newline
|
|
expected = (
|
|
self.without_block
|
|
+ self.marker_start
|
|
+ os.linesep
|
|
+ self.content
|
|
+ self.marker_end
|
|
+ os.linesep
|
|
)
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
@with_tempfile()
|
|
def test_append_no_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when append_if_not_found=True and block doesn't
|
|
exist in file. Test with append_newline explicitly set to False.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
expected = (
|
|
self.without_block
|
|
+ self.marker_start
|
|
+ os.linesep
|
|
+ self.content
|
|
+ self.marker_end
|
|
+ os.linesep
|
|
)
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
# Pass 2: content does not end in newline
|
|
expected = (
|
|
self.without_block
|
|
+ self.marker_start
|
|
+ os.linesep
|
|
+ self.content.rstrip("\r\n")
|
|
+ self.marker_end
|
|
+ os.linesep
|
|
)
|
|
self._write(name, self.without_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), expected)
|
|
|
|
@with_tempfile()
|
|
def test_prepend_auto_line_separator(self, name):
|
|
"""
|
|
This tests the line separator auto-detection when prepending the block
|
|
"""
|
|
# POSIX newlines to Windows newlines
|
|
self._write(name, self.without_block_explicit_windows_newlines)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content_explicit_posix_newlines,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_block_prepended_explicit_windows_newlines
|
|
)
|
|
# Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content_explicit_posix_newlines,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_block_prepended_explicit_windows_newlines
|
|
)
|
|
|
|
# Windows newlines to POSIX newlines
|
|
self._write(name, self.without_block_explicit_posix_newlines)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content_explicit_windows_newlines,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_block_prepended_explicit_posix_newlines
|
|
)
|
|
# Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content_explicit_windows_newlines,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
prepend_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_block_prepended_explicit_posix_newlines
|
|
)
|
|
|
|
@with_tempfile()
|
|
def test_append_auto_line_separator(self, name):
|
|
"""
|
|
This tests the line separator auto-detection when appending the block
|
|
"""
|
|
# POSIX newlines to Windows newlines
|
|
self._write(name, self.without_block_explicit_windows_newlines)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content_explicit_posix_newlines,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_block_appended_explicit_windows_newlines
|
|
)
|
|
# Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content_explicit_posix_newlines,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_block_appended_explicit_windows_newlines
|
|
)
|
|
|
|
# Windows newlines to POSIX newlines
|
|
self._write(name, self.without_block_explicit_posix_newlines)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content_explicit_windows_newlines,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_block_appended_explicit_posix_newlines
|
|
)
|
|
# Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content_explicit_windows_newlines,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_if_not_found=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_block_appended_explicit_posix_newlines
|
|
)
|
|
|
|
@with_tempfile()
|
|
def test_non_matching_block(self, name):
|
|
"""
|
|
Test blockreplace when block exists but its contents are not a
|
|
match.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_non_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_non_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
@with_tempfile()
|
|
def test_non_matching_block_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when block exists but its contents are not a
|
|
match. Test with append_newline explicitly set to True.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_non_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_non_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
@with_tempfile()
|
|
def test_non_matching_block_no_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when block exists but its contents are not a
|
|
match. Test with append_newline explicitly set to False.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_non_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_non_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_matching_block_and_marker_end_not_after_newline
|
|
)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_matching_block_and_marker_end_not_after_newline
|
|
)
|
|
|
|
@with_tempfile()
|
|
def test_non_matching_block_and_marker_not_after_newline(self, name):
|
|
"""
|
|
Test blockreplace when block exists but its contents are not a
|
|
match, and the marker_end is not directly preceded by a newline.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
@with_tempfile()
|
|
def test_non_matching_block_and_marker_not_after_newline_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when block exists but its contents are not a match,
|
|
and the marker_end is not directly preceded by a newline. Test with
|
|
append_newline explicitly set to True.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
@with_tempfile()
|
|
def test_non_matching_block_and_marker_not_after_newline_no_append_newline(
|
|
self, name
|
|
):
|
|
"""
|
|
Test blockreplace when block exists but its contents are not a match,
|
|
and the marker_end is not directly preceded by a newline. Test with
|
|
append_newline explicitly set to False.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_non_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_matching_block_and_marker_end_not_after_newline
|
|
)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_matching_block_and_marker_end_not_after_newline
|
|
)
|
|
|
|
@with_tempfile()
|
|
def test_matching_block(self, name):
|
|
"""
|
|
Test blockreplace when block exists and its contents are a match. No
|
|
changes should be made.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
@with_tempfile()
|
|
def test_matching_block_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when block exists and its contents are a match. Test
|
|
with append_newline explicitly set to True. This will result in an
|
|
extra newline when the content ends in a newline, and will not when the
|
|
content does not end in a newline.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
@with_tempfile()
|
|
def test_matching_block_no_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when block exists and its contents are a match. Test
|
|
with append_newline explicitly set to False. This will result in the
|
|
marker_end not being directly preceded by a newline when the content
|
|
does not end in a newline.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_matching_block)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_matching_block_and_marker_end_not_after_newline
|
|
)
|
|
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_matching_block_and_marker_end_not_after_newline
|
|
)
|
|
|
|
@with_tempfile()
|
|
def test_matching_block_and_marker_not_after_newline(self, name):
|
|
"""
|
|
Test blockreplace when block exists and its contents are a match, but
|
|
the marker_end is not directly preceded by a newline.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
@with_tempfile()
|
|
def test_matching_block_and_marker_not_after_newline_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when block exists and its contents are a match, but
|
|
the marker_end is not directly preceded by a newline. Test with
|
|
append_newline explicitly set to True. This will result in an extra
|
|
newline when the content ends in a newline, and will not when the
|
|
content does not end in a newline.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block_and_extra_newline)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
@with_tempfile()
|
|
def test_matching_block_and_marker_not_after_newline_no_append_newline(self, name):
|
|
"""
|
|
Test blockreplace when block exists and its contents are a match, but
|
|
the marker_end is not directly preceded by a newline. Test with
|
|
append_newline explicitly set to False.
|
|
"""
|
|
# Pass 1: content ends in newline
|
|
self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertTrue(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
# Pass 1a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content,
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(self._read(name), self.with_matching_block)
|
|
|
|
# Pass 2: content does not end in newline
|
|
self._write(name, self.with_matching_block_and_marker_end_not_after_newline)
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_matching_block_and_marker_end_not_after_newline
|
|
)
|
|
# Pass 2a: Re-run state, no changes should be made
|
|
ret = self.run_state(
|
|
"file.blockreplace",
|
|
name=name,
|
|
content=self.content.rstrip("\r\n"),
|
|
marker_start=self.marker_start,
|
|
marker_end=self.marker_end,
|
|
append_newline=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
self.assertFalse(ret[next(iter(ret))]["changes"])
|
|
self.assertEqual(
|
|
self._read(name), self.with_matching_block_and_marker_end_not_after_newline
|
|
)
|
|
|
|
@with_tempfile()
|
|
def test_issue_49043(self, name):
|
|
ret = self.run_function("state.sls", mods="issue-49043", pillar={"name": name},)
|
|
log.error("ret = %s", repr(ret))
|
|
diff = "--- \n+++ \n@@ -0,0 +1,3 @@\n"
|
|
diff += dedent(
|
|
"""\
|
|
+#-- start managed zone --
|
|
+äöü
|
|
+#-- end managed zone --
|
|
"""
|
|
)
|
|
job = "file_|-somefile-blockreplace_|-{}_|-blockreplace".format(name)
|
|
self.assertEqual(ret[job]["changes"]["diff"], diff)
|
|
|
|
|
|
@pytest.mark.windows_whitelisted
|
|
class RemoteFileTest(ModuleCase, SaltReturnAssertsMixin):
|
|
"""
|
|
Uses a local tornado webserver to test http(s) file.managed states with and
|
|
without skip_verify
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.webserver = Webserver()
|
|
cls.webserver.start()
|
|
cls.source = cls.webserver.url("grail/scene33")
|
|
if IS_WINDOWS:
|
|
# CRLF vs LF causes a different hash on windows
|
|
cls.source_hash = "21438b3d5fd2c0028bcab92f7824dc69"
|
|
else:
|
|
cls.source_hash = "d2feb3beb323c79fc7a0f44f1408b4a3"
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
cls.webserver.stop()
|
|
|
|
@with_tempfile(create=False)
|
|
def setUp(self, name): # pylint: disable=arguments-differ
|
|
self.name = name
|
|
|
|
def tearDown(self):
|
|
try:
|
|
os.remove(self.name)
|
|
except OSError as exc:
|
|
if exc.errno != errno.ENOENT:
|
|
raise
|
|
|
|
def run_state(self, *args, **kwargs): # pylint: disable=arguments-differ
|
|
ret = super().run_state(*args, **kwargs)
|
|
log.debug("ret = %s", ret)
|
|
return ret
|
|
|
|
def test_file_managed_http_source_no_hash(self):
|
|
"""
|
|
Test a remote file with no hash
|
|
"""
|
|
ret = self.run_state(
|
|
"file.managed", name=self.name, source=self.source, skip_verify=False
|
|
)
|
|
# This should fail because no hash was provided
|
|
self.assertSaltFalseReturn(ret)
|
|
|
|
def test_file_managed_http_source(self):
|
|
"""
|
|
Test a remote file with no hash
|
|
"""
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=self.name,
|
|
source=self.source,
|
|
source_hash=self.source_hash,
|
|
skip_verify=False,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
def test_file_managed_http_source_skip_verify(self):
|
|
"""
|
|
Test a remote file using skip_verify
|
|
"""
|
|
ret = self.run_state(
|
|
"file.managed", name=self.name, source=self.source, skip_verify=True
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
|
|
def test_file_managed_keep_source_false_http(self):
|
|
"""
|
|
This test ensures that we properly clean the cached file if keep_source
|
|
is set to False, for source files using an http:// URL
|
|
"""
|
|
# Run the state
|
|
ret = self.run_state(
|
|
"file.managed",
|
|
name=self.name,
|
|
source=self.source,
|
|
source_hash=self.source_hash,
|
|
keep_source=False,
|
|
)
|
|
ret = ret[next(iter(ret))]
|
|
assert ret["result"] is True
|
|
|
|
# Now make sure that the file is not cached
|
|
result = self.run_function("cp.is_cached", [self.source])
|
|
assert result == "", "File is still cached at {}".format(result)
|
|
|
|
|
|
@skipIf(not salt.utils.path.which("patch"), "patch is not installed")
|
|
@pytest.mark.windows_whitelisted
|
|
class PatchTest(ModuleCase, SaltReturnAssertsMixin):
|
|
def _check_patch_version(self, min_version):
|
|
"""
|
|
patch version check
|
|
"""
|
|
version = self.run_function("cmd.run", ["patch --version"]).splitlines()[0]
|
|
version = version.split()[1]
|
|
if _LooseVersion(version) < _LooseVersion(min_version):
|
|
self.skipTest(
|
|
"Minimum patch version required: {}. "
|
|
"Patch version installed: {}".format(min_version, version)
|
|
)
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.webserver = Webserver()
|
|
cls.webserver.start()
|
|
|
|
cls.numbers_patch_name = "numbers.patch"
|
|
cls.math_patch_name = "math.patch"
|
|
cls.all_patch_name = "all.patch"
|
|
cls.numbers_patch_template_name = cls.numbers_patch_name + ".jinja"
|
|
cls.math_patch_template_name = cls.math_patch_name + ".jinja"
|
|
cls.all_patch_template_name = cls.all_patch_name + ".jinja"
|
|
|
|
cls.numbers_patch_path = "patches/" + cls.numbers_patch_name
|
|
cls.math_patch_path = "patches/" + cls.math_patch_name
|
|
cls.all_patch_path = "patches/" + cls.all_patch_name
|
|
cls.numbers_patch_template_path = "patches/" + cls.numbers_patch_template_name
|
|
cls.math_patch_template_path = "patches/" + cls.math_patch_template_name
|
|
cls.all_patch_template_path = "patches/" + cls.all_patch_template_name
|
|
|
|
cls.numbers_patch = "salt://" + cls.numbers_patch_path
|
|
cls.math_patch = "salt://" + cls.math_patch_path
|
|
cls.all_patch = "salt://" + cls.all_patch_path
|
|
cls.numbers_patch_template = "salt://" + cls.numbers_patch_template_path
|
|
cls.math_patch_template = "salt://" + cls.math_patch_template_path
|
|
cls.all_patch_template = "salt://" + cls.all_patch_template_path
|
|
|
|
cls.numbers_patch_http = cls.webserver.url(cls.numbers_patch_path)
|
|
cls.math_patch_http = cls.webserver.url(cls.math_patch_path)
|
|
cls.all_patch_http = cls.webserver.url(cls.all_patch_path)
|
|
cls.numbers_patch_template_http = cls.webserver.url(
|
|
cls.numbers_patch_template_path
|
|
)
|
|
cls.math_patch_template_http = cls.webserver.url(cls.math_patch_template_path)
|
|
cls.all_patch_template_http = cls.webserver.url(cls.all_patch_template_path)
|
|
|
|
patches_dir = os.path.join(RUNTIME_VARS.FILES, "file", "base", "patches")
|
|
cls.numbers_patch_hash = salt.utils.hashutils.get_hash(
|
|
os.path.join(patches_dir, cls.numbers_patch_name)
|
|
)
|
|
cls.math_patch_hash = salt.utils.hashutils.get_hash(
|
|
os.path.join(patches_dir, cls.math_patch_name)
|
|
)
|
|
cls.all_patch_hash = salt.utils.hashutils.get_hash(
|
|
os.path.join(patches_dir, cls.all_patch_name)
|
|
)
|
|
cls.numbers_patch_template_hash = salt.utils.hashutils.get_hash(
|
|
os.path.join(patches_dir, cls.numbers_patch_template_name)
|
|
)
|
|
cls.math_patch_template_hash = salt.utils.hashutils.get_hash(
|
|
os.path.join(patches_dir, cls.math_patch_template_name)
|
|
)
|
|
cls.all_patch_template_hash = salt.utils.hashutils.get_hash(
|
|
os.path.join(patches_dir, cls.all_patch_template_name)
|
|
)
|
|
|
|
cls.context = {"two": "two", "ten": 10}
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
cls.webserver.stop()
|
|
|
|
def setUp(self):
|
|
"""
|
|
Create a new unpatched set of files
|
|
"""
|
|
self.base_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
|
|
os.makedirs(os.path.join(self.base_dir, "foo", "bar"))
|
|
self.numbers_file = os.path.join(self.base_dir, "foo", "numbers.txt")
|
|
self.math_file = os.path.join(self.base_dir, "foo", "bar", "math.txt")
|
|
with salt.utils.files.fopen(self.numbers_file, "w") as fp_:
|
|
fp_.write(
|
|
textwrap.dedent(
|
|
"""\
|
|
one
|
|
two
|
|
three
|
|
|
|
1
|
|
2
|
|
3
|
|
"""
|
|
)
|
|
)
|
|
with salt.utils.files.fopen(self.math_file, "w") as fp_:
|
|
fp_.write(
|
|
textwrap.dedent(
|
|
"""\
|
|
Five plus five is ten
|
|
|
|
Four squared is sixteen
|
|
"""
|
|
)
|
|
)
|
|
|
|
self.addCleanup(shutil.rmtree, self.base_dir, ignore_errors=True)
|
|
|
|
def test_patch_single_file(self):
|
|
"""
|
|
Test file.patch using a patch applied to a single file
|
|
"""
|
|
ret = self.run_state(
|
|
"file.patch", name=self.numbers_file, source=self.numbers_patch,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch successfully applied")
|
|
|
|
# Re-run the state, should succeed and there should be a message about
|
|
# a partially-applied hunk.
|
|
ret = self.run_state(
|
|
"file.patch", name=self.numbers_file, source=self.numbers_patch,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
def test_patch_directory(self):
|
|
"""
|
|
Test file.patch using a patch applied to a directory, with changes
|
|
spanning multiple files.
|
|
"""
|
|
self._check_patch_version("2.6")
|
|
ret = self.run_state(
|
|
"file.patch", name=self.base_dir, source=self.all_patch, strip=1,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch successfully applied")
|
|
|
|
# Re-run the state, should succeed and there should be a message about
|
|
# a partially-applied hunk.
|
|
ret = self.run_state(
|
|
"file.patch", name=self.base_dir, source=self.all_patch, strip=1,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
def test_patch_strip_parsing(self):
|
|
"""
|
|
Test that we successfuly parse -p/--strip when included in the options
|
|
"""
|
|
self._check_patch_version("2.6")
|
|
# Run the state using -p1
|
|
ret = self.run_state(
|
|
"file.patch", name=self.base_dir, source=self.all_patch, options="-p1",
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch successfully applied")
|
|
|
|
# Re-run the state using --strip=1
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.base_dir,
|
|
source=self.all_patch,
|
|
options="--strip=1",
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
# Re-run the state using --strip 1
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.base_dir,
|
|
source=self.all_patch,
|
|
options="--strip 1",
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
def test_patch_saltenv(self):
|
|
"""
|
|
Test that we attempt to download the patch from a non-base saltenv
|
|
"""
|
|
# This state will fail because we don't have a patch file in that
|
|
# environment, but that is OK, we just want to test that we're looking
|
|
# in an environment other than base.
|
|
ret = self.run_state(
|
|
"file.patch", name=self.math_file, source=self.math_patch, saltenv="prod",
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(
|
|
ret["comment"],
|
|
"Source file {} not found in saltenv 'prod'".format(self.math_patch),
|
|
)
|
|
|
|
def test_patch_single_file_failure(self):
|
|
"""
|
|
Test file.patch using a patch applied to a single file. This tests a
|
|
failed patch.
|
|
"""
|
|
# Empty the file to ensure that the patch doesn't apply cleanly
|
|
with salt.utils.files.fopen(self.numbers_file, "w"):
|
|
pass
|
|
|
|
ret = self.run_state(
|
|
"file.patch", name=self.numbers_file, source=self.numbers_patch,
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertIn("Patch would not apply cleanly", ret["comment"])
|
|
|
|
# Test the reject_file option and ensure that the rejects are written
|
|
# to the path specified.
|
|
reject_file = salt.utils.files.mkstemp()
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.numbers_file,
|
|
source=self.numbers_patch,
|
|
reject_file=reject_file,
|
|
strip=1,
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertIn("Patch would not apply cleanly", ret["comment"])
|
|
self.assertRegex(
|
|
ret["comment"], "saving rejects to (file )?{}".format(reject_file)
|
|
)
|
|
|
|
def test_patch_directory_failure(self):
|
|
"""
|
|
Test file.patch using a patch applied to a directory, with changes
|
|
spanning multiple files.
|
|
"""
|
|
# Empty the file to ensure that the patch doesn't apply
|
|
with salt.utils.files.fopen(self.math_file, "w"):
|
|
pass
|
|
|
|
ret = self.run_state(
|
|
"file.patch", name=self.base_dir, source=self.all_patch, strip=1,
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertIn("Patch would not apply cleanly", ret["comment"])
|
|
|
|
# Test the reject_file option and ensure that the rejects are written
|
|
# to the path specified.
|
|
reject_file = salt.utils.files.mkstemp()
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.base_dir,
|
|
source=self.all_patch,
|
|
reject_file=reject_file,
|
|
strip=1,
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertIn("Patch would not apply cleanly", ret["comment"])
|
|
self.assertRegex(
|
|
ret["comment"], "saving rejects to (file )?{}".format(reject_file)
|
|
)
|
|
|
|
def test_patch_single_file_remote_source(self):
|
|
"""
|
|
Test file.patch using a patch applied to a single file, with the patch
|
|
coming from a remote source.
|
|
"""
|
|
# Try without a source_hash and without skip_verify=True, this should
|
|
# fail with a message about the source_hash
|
|
ret = self.run_state(
|
|
"file.patch", name=self.math_file, source=self.math_patch_http,
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertIn("Unable to verify upstream hash", ret["comment"])
|
|
|
|
# Re-run the state with a source hash, it should now succeed
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.math_file,
|
|
source=self.math_patch_http,
|
|
source_hash=self.math_patch_hash,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch successfully applied")
|
|
|
|
# Re-run again, this time with no hash and skip_verify=True to test
|
|
# skipping hash verification
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.math_file,
|
|
source=self.math_patch_http,
|
|
skip_verify=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
def test_patch_directory_remote_source(self):
|
|
"""
|
|
Test file.patch using a patch applied to a directory, with changes
|
|
spanning multiple files, and the patch file coming from a remote
|
|
source.
|
|
"""
|
|
self._check_patch_version("2.6")
|
|
# Try without a source_hash and without skip_verify=True, this should
|
|
# fail with a message about the source_hash
|
|
ret = self.run_state(
|
|
"file.patch", name=self.base_dir, source=self.all_patch_http, strip=1,
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertIn("Unable to verify upstream hash", ret["comment"])
|
|
|
|
# Re-run the state with a source hash, it should now succeed
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.base_dir,
|
|
source=self.all_patch_http,
|
|
source_hash=self.all_patch_hash,
|
|
strip=1,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch successfully applied")
|
|
|
|
# Re-run again, this time with no hash and skip_verify=True to test
|
|
# skipping hash verification
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.base_dir,
|
|
source=self.all_patch_http,
|
|
strip=1,
|
|
skip_verify=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
def test_patch_single_file_template(self):
|
|
"""
|
|
Test file.patch using a patch applied to a single file, with jinja
|
|
templating applied to the patch file.
|
|
"""
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.numbers_file,
|
|
source=self.numbers_patch_template,
|
|
template="jinja",
|
|
context=self.context,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch successfully applied")
|
|
|
|
# Re-run the state, should succeed and there should be a message about
|
|
# a partially-applied hunk.
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.numbers_file,
|
|
source=self.numbers_patch_template,
|
|
template="jinja",
|
|
context=self.context,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
def test_patch_directory_template(self):
|
|
"""
|
|
Test file.patch using a patch applied to a directory, with changes
|
|
spanning multiple files, and with jinja templating applied to the patch
|
|
file.
|
|
"""
|
|
self._check_patch_version("2.6")
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.base_dir,
|
|
source=self.all_patch_template,
|
|
template="jinja",
|
|
context=self.context,
|
|
strip=1,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch successfully applied")
|
|
|
|
# Re-run the state, should succeed and there should be a message about
|
|
# a partially-applied hunk.
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.base_dir,
|
|
source=self.all_patch_template,
|
|
template="jinja",
|
|
context=self.context,
|
|
strip=1,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
def test_patch_single_file_remote_source_template(self):
|
|
"""
|
|
Test file.patch using a patch applied to a single file, with the patch
|
|
coming from a remote source.
|
|
"""
|
|
# Try without a source_hash and without skip_verify=True, this should
|
|
# fail with a message about the source_hash
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.math_file,
|
|
source=self.math_patch_template_http,
|
|
template="jinja",
|
|
context=self.context,
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertIn("Unable to verify upstream hash", ret["comment"])
|
|
|
|
# Re-run the state with a source hash, it should now succeed
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.math_file,
|
|
source=self.math_patch_template_http,
|
|
source_hash=self.math_patch_template_hash,
|
|
template="jinja",
|
|
context=self.context,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch successfully applied")
|
|
|
|
# Re-run again, this time with no hash and skip_verify=True to test
|
|
# skipping hash verification
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.math_file,
|
|
source=self.math_patch_template_http,
|
|
template="jinja",
|
|
context=self.context,
|
|
skip_verify=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
def test_patch_directory_remote_source_template(self):
|
|
"""
|
|
Test file.patch using a patch applied to a directory, with changes
|
|
spanning multiple files, and the patch file coming from a remote
|
|
source.
|
|
"""
|
|
self._check_patch_version("2.6")
|
|
# Try without a source_hash and without skip_verify=True, this should
|
|
# fail with a message about the source_hash
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.base_dir,
|
|
source=self.all_patch_template_http,
|
|
template="jinja",
|
|
context=self.context,
|
|
strip=1,
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertIn("Unable to verify upstream hash", ret["comment"])
|
|
|
|
# Re-run the state with a source hash, it should now succeed
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.base_dir,
|
|
source=self.all_patch_template_http,
|
|
source_hash=self.all_patch_template_hash,
|
|
template="jinja",
|
|
context=self.context,
|
|
strip=1,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch successfully applied")
|
|
|
|
# Re-run again, this time with no hash and skip_verify=True to test
|
|
# skipping hash verification
|
|
ret = self.run_state(
|
|
"file.patch",
|
|
name=self.base_dir,
|
|
source=self.all_patch_template_http,
|
|
template="jinja",
|
|
context=self.context,
|
|
strip=1,
|
|
skip_verify=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
def test_patch_test_mode(self):
|
|
"""
|
|
Test file.patch using test=True
|
|
"""
|
|
# Try without a source_hash and without skip_verify=True, this should
|
|
# fail with a message about the source_hash
|
|
ret = self.run_state(
|
|
"file.patch", name=self.numbers_file, source=self.numbers_patch, test=True,
|
|
)
|
|
self.assertSaltNoneReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "The patch would be applied")
|
|
self.assertTrue(ret["changes"])
|
|
|
|
# Apply the patch for real. We'll then be able to test below that we
|
|
# exit with a True rather than a None result if test=True is used on an
|
|
# already-applied patch.
|
|
ret = self.run_state(
|
|
"file.patch", name=self.numbers_file, source=self.numbers_patch,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch successfully applied")
|
|
self.assertTrue(ret["changes"])
|
|
|
|
# Run again with test=True. Since the pre-check happens before we do
|
|
# the __opts__['test'] check, we should exit with a True result just
|
|
# the same as if we try to run this state on an already-patched file
|
|
# *without* test=True.
|
|
ret = self.run_state(
|
|
"file.patch", name=self.numbers_file, source=self.numbers_patch, test=True,
|
|
)
|
|
self.assertSaltTrueReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertEqual(ret["comment"], "Patch was already applied")
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
# Empty the file to ensure that the patch doesn't apply cleanly
|
|
with salt.utils.files.fopen(self.numbers_file, "w"):
|
|
pass
|
|
|
|
# Run again with test=True. Similar to the above run, we are testing
|
|
# that we return before we reach the __opts__['test'] check. In this
|
|
# case we should return a False result because we should already know
|
|
# by this point that the patch will not apply cleanly.
|
|
ret = self.run_state(
|
|
"file.patch", name=self.numbers_file, source=self.numbers_patch, test=True,
|
|
)
|
|
self.assertSaltFalseReturn(ret)
|
|
ret = ret[next(iter(ret))]
|
|
self.assertIn("Patch would not apply cleanly", ret["comment"])
|
|
self.assertEqual(ret["changes"], {})
|
|
|
|
|
|
WIN_TEST_FILE = "c:/testfile"
|
|
|
|
|
|
@pytest.mark.destructive_test
|
|
@skipIf(not IS_WINDOWS, "windows test only")
|
|
@pytest.mark.windows_whitelisted
|
|
class WinFileTest(ModuleCase):
|
|
"""
|
|
Test for the file state on Windows
|
|
"""
|
|
|
|
def setUp(self):
|
|
self.run_state(
|
|
"file.managed", name=WIN_TEST_FILE, makedirs=True, contents="Only a test"
|
|
)
|
|
|
|
def tearDown(self):
|
|
self.run_state("file.absent", name=WIN_TEST_FILE)
|
|
|
|
def test_file_managed(self):
|
|
"""
|
|
Test file.managed on Windows
|
|
"""
|
|
self.assertTrue(self.run_state("file.exists", name=WIN_TEST_FILE))
|
|
|
|
def test_file_copy(self):
|
|
"""
|
|
Test file.copy on Windows
|
|
"""
|
|
ret = self.run_state(
|
|
"file.copy", name="c:/testfile_copy", makedirs=True, source=WIN_TEST_FILE
|
|
)
|
|
self.assertTrue(ret)
|
|
|
|
def test_file_comment(self):
|
|
"""
|
|
Test file.comment on Windows
|
|
"""
|
|
self.run_state("file.comment", name=WIN_TEST_FILE, regex="^Only")
|
|
with salt.utils.files.fopen(WIN_TEST_FILE, "r") as fp_:
|
|
self.assertTrue(fp_.read().startswith("#Only"))
|
|
|
|
def test_file_replace(self):
|
|
"""
|
|
Test file.replace on Windows
|
|
"""
|
|
self.run_state(
|
|
"file.replace", name=WIN_TEST_FILE, pattern="test", repl="testing"
|
|
)
|
|
with salt.utils.files.fopen(WIN_TEST_FILE, "r") as fp_:
|
|
self.assertIn("testing", fp_.read())
|
|
|
|
def test_file_absent(self):
|
|
"""
|
|
Test file.absent on Windows
|
|
"""
|
|
ret = self.run_state("file.absent", name=WIN_TEST_FILE)
|
|
self.assertTrue(ret)
|