mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-05-06 13:02:37 +00:00
* Add kubeconfig module for managing Kubernetes config files * Remove unnecessary requirement & Change version * Move functions to module_utils * Add unit tests * Add kubeconfig module for managing Kubernetes config files * Remove unnecessary requirement & Change version * Move functions to module_utils * Add unit tests * Avoid linter errors * Improve documentation clarity * Redact sensitive kubeconfig information * Imprvoe verbosity * Move import statement for to_native to avoid linters check failure * Fix linting error --------- Co-authored-by: Bianca Henderson <bianca@redhat.com>
232 lines
6.6 KiB
Python
232 lines
6.6 KiB
Python
# Copyright (c) Ansible Project
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
import yaml
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.kubeconfig import (
|
|
deep_merge,
|
|
hash_data,
|
|
load_yaml_file,
|
|
merge_by_name,
|
|
write_file,
|
|
)
|
|
|
|
|
|
# load_yaml_file
|
|
def test_load_yaml_file_returns_empty_dict_for_missing_file():
|
|
assert load_yaml_file("/nonexistent/path/config") == {}
|
|
|
|
|
|
def test_load_yaml_file_returns_empty_dict_for_none():
|
|
assert load_yaml_file(None) == {}
|
|
|
|
|
|
def test_load_yaml_file_returns_empty_dict_for_empty_string():
|
|
assert load_yaml_file("") == {}
|
|
|
|
|
|
def test_load_yaml_file_loads_valid_yaml(tmp_path):
|
|
config = {"apiVersion": "v1", "kind": "Config", "clusters": []}
|
|
f = tmp_path / "config"
|
|
f.write_text(yaml.safe_dump(config))
|
|
assert load_yaml_file(str(f)) == config
|
|
|
|
|
|
def test_load_yaml_file_returns_empty_dict_for_empty_file(tmp_path):
|
|
f = tmp_path / "config"
|
|
f.write_text("")
|
|
assert load_yaml_file(str(f)) == {}
|
|
|
|
|
|
# deep_merge
|
|
def test_deep_merge_adds_new_keys():
|
|
base = {"a": 1}
|
|
updates = {"b": 2}
|
|
assert deep_merge(base, updates) == {"a": 1, "b": 2}
|
|
|
|
|
|
def test_deep_merge_overwrites_scalar():
|
|
base = {"a": 1}
|
|
updates = {"a": 99}
|
|
assert deep_merge(base, updates) == {"a": 99}
|
|
|
|
|
|
def test_deep_merge_recursively_merges_dicts():
|
|
base = {
|
|
"cluster": {
|
|
"server": "https://old.example.com",
|
|
"insecure-skip-tls-verify": True,
|
|
}
|
|
}
|
|
updates = {"cluster": {"server": "https://new.example.com"}}
|
|
result = deep_merge(base, updates)
|
|
assert result["cluster"]["server"] == "https://new.example.com"
|
|
assert result["cluster"]["insecure-skip-tls-verify"] is True
|
|
|
|
|
|
def test_deep_merge_does_not_mutate_base():
|
|
base = {"a": {"b": 1}}
|
|
updates = {"a": {"c": 2}}
|
|
deep_merge(base, updates)
|
|
assert base == {"a": {"b": 1}}
|
|
|
|
|
|
def test_deep_merge_overwrites_dict_with_scalar():
|
|
base = {"a": {"nested": 1}}
|
|
updates = {"a": "flat"}
|
|
assert deep_merge(base, updates) == {"a": "flat"}
|
|
|
|
|
|
# merge_by_name
|
|
def test_merge_by_name_adds_new_entry():
|
|
existing = []
|
|
new = [{"name": "cluster-a", "cluster": {"server": "https://a.example.com"}}]
|
|
result = merge_by_name(existing, new)
|
|
assert len(result) == 1
|
|
assert result[0]["name"] == "cluster-a"
|
|
|
|
|
|
def test_merge_by_name_preserves_existing_when_no_new():
|
|
existing = [{"name": "cluster-a", "cluster": {"server": "https://a.example.com"}}]
|
|
result = merge_by_name(existing, [])
|
|
assert len(result) == 1
|
|
assert result[0]["name"] == "cluster-a"
|
|
|
|
|
|
def test_merge_by_name_default_behavior_merges_fields():
|
|
existing = [
|
|
{
|
|
"name": "cluster-a",
|
|
"cluster": {"server": "https://old.com", "insecure-skip-tls-verify": True},
|
|
}
|
|
]
|
|
new = [{"name": "cluster-a", "cluster": {"server": "https://new.com"}}]
|
|
result = merge_by_name(existing, new)
|
|
assert len(result) == 1
|
|
assert result[0]["cluster"]["server"] == "https://new.com"
|
|
assert result[0]["cluster"]["insecure-skip-tls-verify"] is True
|
|
|
|
|
|
def test_merge_by_name_replace_behavior_replaces_entire_entry():
|
|
existing = [
|
|
{
|
|
"name": "cluster-a",
|
|
"cluster": {"server": "https://old.com", "insecure-skip-tls-verify": True},
|
|
}
|
|
]
|
|
new = [
|
|
{
|
|
"name": "cluster-a",
|
|
"behavior": "replace",
|
|
"cluster": {"server": "https://new.com"},
|
|
}
|
|
]
|
|
result = merge_by_name(existing, new)
|
|
assert result[0]["cluster"] == {"server": "https://new.com"}
|
|
assert "insecure-skip-tls-verify" not in result[0]["cluster"]
|
|
|
|
|
|
def test_merge_by_name_keep_behavior_preserves_existing():
|
|
existing = [{"name": "cluster-a", "cluster": {"server": "https://old.com"}}]
|
|
new = [
|
|
{
|
|
"name": "cluster-a",
|
|
"behavior": "keep",
|
|
"cluster": {"server": "https://new.com"},
|
|
}
|
|
]
|
|
result = merge_by_name(existing, new)
|
|
assert result[0]["cluster"]["server"] == "https://old.com"
|
|
|
|
|
|
def test_merge_by_name_behavior_key_not_in_output():
|
|
existing = []
|
|
new = [
|
|
{
|
|
"name": "cluster-a",
|
|
"behavior": "replace",
|
|
"cluster": {"server": "https://a.com"},
|
|
}
|
|
]
|
|
result = merge_by_name(existing, new)
|
|
assert "behavior" not in result[0]
|
|
|
|
|
|
def test_merge_by_name_skips_items_without_name():
|
|
existing = []
|
|
new = [{"cluster": {"server": "https://a.com"}}]
|
|
result = merge_by_name(existing, new)
|
|
assert result == []
|
|
|
|
|
|
def test_merge_by_name_skips_non_dict_items():
|
|
existing = []
|
|
new = ["not-a-dict", 42]
|
|
result = merge_by_name(existing, new)
|
|
assert result == []
|
|
|
|
|
|
def test_merge_by_name_adds_multiple_new_entries():
|
|
existing = []
|
|
new = [
|
|
{"name": "cluster-a", "cluster": {"server": "https://a.com"}},
|
|
{"name": "cluster-b", "cluster": {"server": "https://b.com"}},
|
|
]
|
|
result = merge_by_name(existing, new)
|
|
names = [r["name"] for r in result]
|
|
assert "cluster-a" in names
|
|
assert "cluster-b" in names
|
|
|
|
|
|
def test_merge_by_name_existing_non_dict_items_are_skipped():
|
|
existing = ["not-a-dict", {"cluster": {"server": "https://a.com"}}]
|
|
new = [{"name": "cluster-b", "cluster": {"server": "https://b.com"}}]
|
|
result = merge_by_name(existing, new)
|
|
assert len(result) == 1
|
|
assert result[0]["name"] == "cluster-b"
|
|
|
|
|
|
# hash_data
|
|
def test_hash_data_returns_string():
|
|
assert isinstance(hash_data({}), str)
|
|
|
|
|
|
def test_hash_data_different_input_different_hash():
|
|
assert hash_data({"a": 1}) != hash_data({"a": 2})
|
|
|
|
|
|
def test_hash_data_order_independent():
|
|
a = {"x": 1, "y": 2}
|
|
b = {"y": 2, "x": 1}
|
|
assert hash_data(a) == hash_data(b)
|
|
|
|
|
|
# write_file
|
|
def test_write_file_returns_false_for_empty_dest():
|
|
assert write_file("", {"apiVersion": "v1"}) is False
|
|
|
|
|
|
def test_write_file_returns_false_for_none_dest():
|
|
assert write_file(None, {"apiVersion": "v1"}) is False
|
|
|
|
|
|
def test_write_file_writes_valid_yaml(tmp_path):
|
|
dest = str(tmp_path / "config")
|
|
data = {"apiVersion": "v1", "kind": "Config"}
|
|
result = write_file(dest, data)
|
|
assert result is True
|
|
with open(dest, "r") as f:
|
|
written = yaml.safe_load(f)
|
|
assert written == data
|
|
|
|
|
|
def test_write_file_overwrites_existing_file(tmp_path):
|
|
dest = str(tmp_path / "config")
|
|
write_file(dest, {"apiVersion": "v1"})
|
|
write_file(dest, {"apiVersion": "v2"})
|
|
with open(dest, "r") as f:
|
|
written = yaml.safe_load(f)
|
|
assert written["apiVersion"] == "v2"
|