Skip to content

Commit

Permalink
Don't use Salt's custom YAML loader to load static grains file
Browse files Browse the repository at this point in the history
  • Loading branch information
terminalmage committed Apr 26, 2024
1 parent 47ad770 commit a401152
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 4 deletions.
5 changes: 3 additions & 2 deletions salt/grains/extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
import logging
import os

import yaml

import salt.utils
import salt.utils.data
import salt.utils.files
import salt.utils.path
import salt.utils.platform
import salt.utils.yaml

__proxyenabled__ = ["*"]
log = logging.getLogger(__name__)
Expand Down Expand Up @@ -56,7 +57,7 @@ def config():
log.debug("Loading static grains from %s", gfn)
with salt.utils.files.fopen(gfn, "rb") as fp_:
try:
return salt.utils.data.decode(salt.utils.yaml.safe_load(fp_))
return salt.utils.data.decode(yaml.safe_load(fp_))
except Exception: # pylint: disable=broad-except
log.warning("Bad syntax in grains file! Skipping.")
return {}
Expand Down
7 changes: 5 additions & 2 deletions salt/modules/grains.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from collections.abc import Mapping
from functools import reduce # pylint: disable=redefined-builtin

import yaml

import salt.utils.compat
import salt.utils.data
import salt.utils.files
Expand Down Expand Up @@ -262,8 +264,9 @@ def setvals(grains, destructive=False, refresh_pillar=True):
if os.path.isfile(gfn):
with salt.utils.files.fopen(gfn, "rb") as fp_:
try:
grains = salt.utils.yaml.safe_load(fp_)
except salt.utils.yaml.YAMLError as exc:
# DO NOT USE salt.utils.yaml.safe_load HERE
grains = yaml.safe_load(fp_)
except Exception as exc: # pylint: disable=broad-except
return f"Unable to read existing grains file: {exc}"
if not isinstance(grains, dict):
grains = {}
Expand Down
30 changes: 30 additions & 0 deletions tests/pytests/unit/grains/test_extra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
tests.pytests.unit.grains.test_extra
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""

import pytest

import salt.grains.extra as extra
import salt.utils.platform
from tests.support.mock import MagicMock, patch


@pytest.fixture
def configure_loader_modules():
return {extra: {}}


def test_static_grains_file_conflicting_keys(tmp_path):
"""
Test that static grains files with conflicting keys don't result in failure
to load all grains from that file.
"""
with (tmp_path / "grains").open("w") as fh:
fh.write('foo: bar\nfoo: baz\nno_conflict_here: "yay"\n')

with patch.object(
salt.utils.platform, "is_proxy", MagicMock(return_value=False)
), patch("salt.grains.extra.__opts__", {"conf_file": str(tmp_path / "minion")}):
static_grains = extra.config()
assert "no_conflict_here" in static_grains
29 changes: 29 additions & 0 deletions tests/pytests/unit/modules/test_grains.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os

import pytest
import yaml

import salt.modules.grains as grainsmod
import salt.utils.dictupdate as dictupdate
Expand Down Expand Up @@ -689,6 +690,34 @@ def test_setval_unicode():
assert ret[key] == value


def test_setvals_conflicting_ids(tmp_path):
"""
Test that conflicting top-level keys in static grains file does not break
managing static grains
"""
static_grains = tmp_path / "grains"
with static_grains.open("w") as fh:
fh.write('foo: bar\nfoo: baz\nno_conflict_here: "yay"\n')

key = "hello"
value = "world"

with patch.dict(grainsmod.__opts__, {"conf_file": str(tmp_path / "minion")}):
# Update the grains file in the temp dir
grainsmod.setvals({key: value})

# Load the contents of the file as text for the below asserts
updated_grains = static_grains.read_text()

top_level_keys = [
line.split()[0].rstrip(":") for line in updated_grains.splitlines()
]
# Confirm that the conflicting IDs were collapsed into a single value
assert top_level_keys.count("foo") == 1
# Confirm that the new static grain made it into the file
assert yaml.safe_load(updated_grains)[key] == value


def test_delval_single():
with patch.dict(
grainsmod.__grains__, {"a": "aval", "b": {"nested": "val"}, "c": 8}
Expand Down

0 comments on commit a401152

Please sign in to comment.