Skip to content

Commit

Permalink
feat: add tests for resource model source
Browse files Browse the repository at this point in the history
  • Loading branch information
blu-base committed May 7, 2024
1 parent 275be10 commit ef46b3b
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 39 deletions.
73 changes: 38 additions & 35 deletions contents/salt_resource_model_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
log = logging.getLogger(__name__)


def configure_logging(log_level):
def configure_logging(log_level: str):
"""
Configure logging based on the provided log level.
"""
log_level = 'ERROR' if log_level != 'DEBUG' else 'DEBUG'
log.setLevel(logging.getLevelName(log_level))


def validate_required_inputs(data):
def validate_required_inputs(data: dict):
"""
Validate required data items provided by Rundeck
"""
Expand All @@ -35,29 +35,27 @@ def validate_required_inputs(data):
data['tgt'] = '*'


def tag_grains(data):
grains = set()
if data.get('tags') is not None:
grains = set(data['tags'].split(','))

return grains
def string_to_unique_set(src: str) -> set:
"""
Return set with unique values from input string.
"""
if isinstance(src, str):
ret = set(src.split(','))
ret.discard('')

return ret

def attributes_grains(data):
grains = set()
if data.get('attributes') is not None:
grains = set(data.get('attributes', '').split(','))
return grains
return set()


def prepare_grains(data):
def prepare_grains(data: dict) -> set:
"""
Prepare grains for tags and attributes.
"""
needed_grains = set(['id', 'cpuarch', 'os', 'os_family', 'osrelease'])

needed_tags = tag_grains(data)
needed_attributes = attributes_grains(data)
needed_tags = string_to_unique_set(data.get('tags', None))
needed_attributes = string_to_unique_set(data.get('attributes', None))

log.debug(f'Tag grains: {needed_tags}')
log.debug(f'Attribute grains: {needed_attributes}')
Expand Down Expand Up @@ -107,7 +105,7 @@ def collect_minions_grains(data, all_needed_grains):
return minions


def get_os_family(os_family):
def get_os_family(os_family: str) -> str:
"""
Map OS family to a standardized format.
"""
Expand All @@ -121,33 +119,38 @@ def get_os_family(os_family):
return os_family_map.get(os_family, os_family)


def process_tags(grains, needed_tags):
def process_tags(metadata: dict, needed_tags: set) -> set:
"""
Extract tags from grains.
Extract tags from grains or pillar.
"""
tags = []
tags = set()
for tag in needed_tags:
tag_value = grains.get(tag, None)
if isinstance(tag_value, list):
tags.extend(tag_value)
elif tag_value:
tags.append(tag_value)
tag_value = metadata.get(tag)
if tag_value is None:
continue
if isinstance(tag_value, (str, int, float)):
tags.add(str(tag_value))
elif isinstance(tag_value, list):
tags.update(str(elem) for elem in tag_value if isinstance(elem, (str, int, float)))
else:
log.warning(f'The tag {tag} is not a supported type (str, int, float, or a list of these types)')
return tags


def process_attributes(grains, needed_attributes, reserved_keys):
def process_attributes(metadata, needed_attributes, reserved_keys):
"""
Process attributes from grains.
Process attributes from grains or pillar.
"""
processed_attributes = {}
for attribute in needed_attributes:
attribute_name = f'salt-{attribute}' if attribute in reserved_keys else attribute
attribute_value = grains.get(attribute)
if attribute_value:
if isinstance(attribute_value, (str, int)):
processed_attributes[attribute_name] = attribute_value
else:
log.warning(f'The grain {attribute} is not a string. Nested values are not supported attribute values.')
attribute_value = metadata.get(attribute, '')
if isinstance(attribute_value, (str, int, float)):
processed_attributes[attribute_name] = str(attribute_value)
else:
log.warning(f'The attribute {attribute} is not a string. Nested values are not supported attribute values.')
processed_attributes[attribute_name] = ''

return processed_attributes


Expand All @@ -169,10 +172,10 @@ def generate_resource_model(minions, data):
'osName': grains['os'],
'osVersion': grains['osrelease'],
'osFamily': get_os_family(grains['os_family']),
'tags': process_tags(grains, tag_grains(data))
'tags': list(process_tags(grains, string_to_unique_set(data['tags'])))
}

processed_attributes = process_attributes(grains, attributes_grains(data), reserved_keys)
processed_attributes = process_attributes(grains, string_to_unique_set(data['attributes']), reserved_keys)
model.update(processed_attributes)

resource_model[nodename] = model
Expand Down
6 changes: 3 additions & 3 deletions plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,14 @@ providers:
- type: String
name: tags
title: 'Node tags'
description: 'Create node tags from the values of grains. comma-separated list for multiple grains.'
description: 'Create node tags from the values of grains. Use comma-separated list for multiple grains.'
scope: Project
- type: String
name: attributes
title: 'Node attributes'
description: 'Create node attributes from grains and their values. Comma-separated list for multiple grains. Nested values of grains are not supported, but nested keys are.'
description: 'Create node attributes from grains and their values. use comma-separated list for multiple grains. Nested values of grains are not supported, but nested keys are, such as systemd.version .'
scope: Project
default: 'master,'
default: 'master'
- type: Integer
name: timeout
title: 'Minion timeout'
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/test_salt_file_copier.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def test_empty_destination_file(rundeck_environment_base, session_minion_id, ses
assert sys_exit.value.code == 1


@pytest.mark.parametrize("file_length", [1000, 1024*3072])
@pytest.mark.parametrize("file_length", [1000, 1024*2])
def test_file_transfer(rundeck_environment_base, session_minion_id, session_salt_api, file_length, tmp_path, capsys):
assert session_salt_api.is_running()

Expand All @@ -115,6 +115,7 @@ def test_file_transfer(rundeck_environment_base, session_minion_id, session_salt
'RD_NODE_HOSTNAME': session_minion_id,
'RD_FILE_COPY_FILE': str(src),
'RD_FILE_COPY_DESTINATION': str(dest),
'RD_CONFIG_SALT_FILE_COPY_CHUNK_SIZE': str(1024),
})

# create test file
Expand Down
104 changes: 104 additions & 0 deletions tests/unit/test_salt_resource_model_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import pytest

from contents.salt_resource_model_source import string_to_unique_set, prepare_grains, process_tags, process_attributes


@pytest.mark.parametrize(('input_str', 'expected_set'), [
# Test cases for string_to_unique_set function
('', set()), # Empty string returns an empty set
('abc', set(['abc'])), # Single string element returns set with that string
('a,b,c', set(['a', 'b', 'c'])), # Comma-separated string returns set of individual elements
('a,,c', set(['a', 'c'])), # return set without empty elements
([], set()), # Empty list returns an empty set
(None, set()), # None returns an empty set
])
def test_string_to_unique_set(input_str, expected_set):
assert string_to_unique_set(input_str) == expected_set


@pytest.mark.parametrize(('input_data', 'expected_set'), [
# Test cases for prepare_grains function
# Empty input returns set of defaults
({}, set(['id', 'cpuarch', 'os', 'os_family', 'osrelease'])),
# Empty tags and attributes return set of defaults
({'tags': '', 'attributes': ''}, set(['id', 'cpuarch', 'os', 'os_family', 'osrelease'])),
# Tags and attributes with duplicate values return set with default keys and that value
({'tags': 'abc', 'attributes': 'abc'}, set(['id', 'cpuarch', 'os', 'os_family', 'osrelease', 'abc'])),
# Tags and attributes with multiple values return set with default keys and those values
({'tags': 'abc', 'attributes': 'a,b'}, set(['id', 'cpuarch', 'os', 'os_family', 'osrelease', 'abc', 'a', 'b'])),
])
def test_prepare_grains(input_data, expected_set):
assert prepare_grains(input_data) == expected_set


@pytest.mark.parametrize(('needed_tags', 'input_data', 'expected_list'), [
# Test cases for process_tags function
(set(), {}, set()), # Empty needed_tags and empty input_data return an empty set
(set(), {'a': 'b'}, set()), # Empty needed_tags and non-empty input_data return an empty set
# tag with matching key in input_data returns set with the corresponding value
(set(['plainkey']), {'plainkey': 'a', 'otherkey': 'val'}, set(['a'])),
# integer tag with matching key in input_data returns set with the corresponding value
(set(['plainkey']), {'plainkey': 1}, set(['1'])),
# Single needed tag with list value in input_data returns set with all elements of the list
(set(['keyoflist']), {'keyoflist': ['a', 'b']}, set(['a', 'b'])),
# Single needed tag with non-list, non-string value in input_data returns an empty set
(set(['ignorenested']), {'ignorenested': {'key': 'value'}}, set()),
# tag not present in input_data returns an empty set
(set(['missinggrain']), {'othergrain': 'value'}, set()),
# Nested tag with single string value in input_data returns set with that value
(set(['nested.plainkey']), {'nested.plainkey': 'a'}, set(['a'])),
# Nested tag with list value in input_data returns set with all elements of the list
(set(['nested.keyoflist']), {'nested.keyoflist': ['a', 'b']}, set(['a', 'b'])),
# Nested tag with non-list, non-string value in input_data returns an empty set
(set(['nested.ignorenested']), {'nested.ignorenested': {'key': 'value'}}, set()),
# Duplicate values in single value and list element
(set(['tag1', 'tag2']), {'tag1': 'val1', 'tag2': ['val1', 'val2']}, set(['val1', 'val2'])),
])
def test_process_tags(input_data, needed_tags, expected_list):
assert process_tags(input_data, needed_tags) == expected_list


@pytest.mark.parametrize(('needed_attributes', 'input_data', 'reserved_keys', 'expected_list'), [
# Test cases for process_attributes function
# Empty Attributes and input_data
(set(), {}, set(), {}),
# Empty needed_attributes and non-empty input_data
(set(), {'unused_key': 'val'}, set(), {}),
# Attribute present in input_data
(set(['plainkey']), {'plainkey': 'val', 'otherkey': 'val2'}, set(), {'plainkey': 'val'}),
# Single attribute with integer value present in input_data
(set(['plainkey']), {'plainkey': 1, }, set(), {'plainkey': '1'}),
# Attribute with reserved key present in input_data
(set(['reservedkey']), {'reservedkey': 'val'}, set(['reservedkey']), {'salt-reservedkey': 'val'}),
# Attribute not present in input_data
(set(['missingkey']), {}, set(), {'missingkey': ''}),
# No values for attribute with list value present in input_data
(set(['listkey']), {'listkey': ['val1', 'val2']}, set(), {'listkey': ''}),
# No values for attribute with dict value present in input_data
(set(['nestedkey']), {'nestedkey': {'key': 'val'}}, set(), {'nestedkey': ''}),
])
def test_process_attributes(input_data, needed_attributes, reserved_keys, expected_list):
assert process_attributes(input_data, needed_attributes, reserved_keys) == expected_list

0 comments on commit ef46b3b

Please sign in to comment.