Skip to content

Commit 9002611

Browse files
committed
refactor: ha_cluster_info: pcs permissions
split responsibilities between a loader and an exporter
1 parent b3a97cc commit 9002611

File tree

6 files changed

+235
-112
lines changed

6 files changed

+235
-112
lines changed

library/ha_cluster_info.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,13 @@ def export_pcsd_configuration() -> Dict[str, Any]:
138138
"""
139139
result: dict[str, Any] = dict()
140140

141-
pcs_permissions = loader.get_pcsd_local_cluster_permissions()
142-
if pcs_permissions is not None:
143-
result["ha_cluster_pcs_permission_list"] = pcs_permissions
141+
pcsd_settings_dict = loader.get_pcsd_settings_conf()
142+
if pcsd_settings_dict is not None:
143+
pcs_permissions = exporter.export_pcs_permission_list(
144+
pcsd_settings_dict
145+
)
146+
if pcs_permissions is not None:
147+
result["ha_cluster_pcs_permission_list"] = pcs_permissions
144148

145149
return result
146150

module_utils/ha_cluster_lsr/info/exporter.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
__metaclass__ = type
1313

1414
from contextlib import contextmanager
15-
from typing import Any, Dict, Iterator, List
15+
from typing import Any, Dict, Iterator, List, Optional
1616

1717

1818
class JsonMissingKey(Exception):
@@ -216,3 +216,32 @@ def export_cluster_nodes(
216216
# finish one node export
217217
node_list.append(one_node)
218218
return node_list
219+
220+
221+
def export_pcs_permission_list(
222+
pcs_settings_conf_dict: Dict[str, Any],
223+
) -> Optional[List[Dict[str, Any]]]:
224+
"""
225+
Extract local cluster permissions from pcs_settings config or None on error
226+
227+
pcs_settings -- JSON parsed pcs_settings.conf
228+
"""
229+
# Currently, only format version 2 is in use, so we don't check for file
230+
# format version
231+
result: List[Dict[str, Any]] = []
232+
with _handle_missing_key(pcs_settings_conf_dict, "pcs_settings.conf"):
233+
permission_dict = pcs_settings_conf_dict["permissions"]
234+
if not isinstance(permission_dict, dict):
235+
return None
236+
permission_list = permission_dict["local_cluster"]
237+
if not isinstance(permission_list, list):
238+
return None
239+
for permission_item in permission_list:
240+
result.append(
241+
{
242+
"type": permission_item["type"],
243+
"name": permission_item["name"],
244+
"allow_list": list(permission_item["allow"]),
245+
}
246+
)
247+
return result

module_utils/ha_cluster_lsr/info/loader.py

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -218,42 +218,19 @@ def get_pcsd_known_hosts() -> Dict[str, str]:
218218
raise JsonParseError(str(e), "not logging data", "known hosts") from e
219219

220220

221-
def get_pcsd_local_cluster_permissions() -> Optional[List[Dict[str, Any]]]:
221+
def get_pcsd_settings_conf() -> Optional[Dict[str, Any]]:
222222
"""
223-
Load pcsd local cluster permissions
223+
Load pcsd config file pcs_settings.conf
224224
"""
225225
# There is no pcs interface for the file yet, so we parse the file directly.
226226
if not os.path.exists(PCSD_SETTINGS_PATH):
227227
return None
228228

229-
result: List[Dict[str, Any]] = []
230229
try:
231230
with open(
232231
PCSD_SETTINGS_PATH, "r", encoding="utf-8"
233232
) as pcsd_settings_file:
234-
pcsd_settings = json.load(pcsd_settings_file)
235-
permission_dict = pcsd_settings.get("permissions")
236-
if not isinstance(permission_dict, dict):
237-
return result
238-
permission_list = permission_dict.get("local_cluster")
239-
if not isinstance(permission_list, list):
240-
return result
241-
for permission_item in permission_list:
242-
if (
243-
"type" not in permission_item
244-
or "name" not in permission_item
245-
or "allow" not in permission_item
246-
or not isinstance(permission_item["allow"], list)
247-
):
248-
continue
249-
result.append(
250-
{
251-
"type": permission_item["type"],
252-
"name": permission_item["name"],
253-
"allow_list": list(permission_item["allow"]),
254-
}
255-
)
256-
return result
233+
file_data = pcsd_settings_file.read()
234+
return json.loads(file_data)
257235
except json.JSONDecodeError as e:
258-
# cannot show actual data as they contain sensitive information
259-
raise JsonParseError(str(e), "not logging data", "pcsd settings") from e
236+
raise JsonParseError(str(e), file_data, "pcsd settings") from e

tests/unit/test_ha_cluster_info.py

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -371,48 +371,96 @@ def test_non_rhel(self) -> None:
371371
class ExportPcsdConfiguration(TestCase):
372372
maxDiff = None
373373

374-
@mock.patch("ha_cluster_info.loader.get_pcsd_local_cluster_permissions")
375-
def test_no_permissions(self, mock_load_pcsd_permisions: mock.Mock) -> None:
376-
mock_load_pcsd_permisions.return_value = None
377-
378-
self.assertEqual(
379-
ha_cluster_info.export_pcsd_configuration(),
380-
{},
381-
)
382-
383-
@mock.patch("ha_cluster_info.loader.get_pcsd_local_cluster_permissions")
374+
@mock.patch("ha_cluster_info.loader.get_pcsd_settings_conf")
384375
def test_permissions_defined(
385376
self, mock_load_pcsd_permisions: mock.Mock
386377
) -> None:
387-
mock_load_pcsd_permisions.return_value = [
388-
{
389-
"type": "group",
390-
"name": "haclient",
391-
"allow": ["grant", "read", "write"],
392-
},
393-
{
394-
"type": "user",
395-
"name": "admin",
396-
"allow": ["full"],
397-
},
398-
]
378+
mock_load_pcsd_permisions.return_value = {
379+
"permissions": {
380+
"local_cluster": [
381+
{
382+
"type": "group",
383+
"name": "haclient",
384+
"allow": ["grant", "read", "write"],
385+
},
386+
{
387+
"type": "user",
388+
"name": "admin",
389+
"allow": ["full"],
390+
},
391+
]
392+
}
393+
}
399394

400395
self.assertEqual(
401396
ha_cluster_info.export_pcsd_configuration(),
402397
{
403-
"ha_cluster_pcs_permission_list": mock_load_pcsd_permisions.return_value,
398+
"ha_cluster_pcs_permission_list": [
399+
{
400+
"type": "group",
401+
"name": "haclient",
402+
"allow_list": ["grant", "read", "write"],
403+
},
404+
{
405+
"type": "user",
406+
"name": "admin",
407+
"allow_list": ["full"],
408+
},
409+
]
404410
},
405411
)
406412

407-
@mock.patch("ha_cluster_info.loader.get_pcsd_local_cluster_permissions")
413+
@mock.patch("ha_cluster_info.loader.get_pcsd_settings_conf")
408414
def test_empty_permissions_defined(
409415
self, mock_load_pcsd_permisions: mock.Mock
410416
) -> None:
411-
mock_load_pcsd_permisions.return_value = []
417+
mock_load_pcsd_permisions.return_value = {
418+
"permissions": {
419+
"local_cluster": [],
420+
}
421+
}
412422

413423
self.assertEqual(
414424
ha_cluster_info.export_pcsd_configuration(),
415425
{
416426
"ha_cluster_pcs_permission_list": [],
417427
},
418428
)
429+
430+
@mock.patch("ha_cluster_info.loader.get_pcsd_settings_conf")
431+
def test_permission_load_error(
432+
self, mock_load_pcsd_permisions: mock.Mock
433+
) -> None:
434+
mock_load_pcsd_permisions.return_value = None
435+
436+
self.assertEqual(
437+
ha_cluster_info.export_pcsd_configuration(),
438+
{},
439+
)
440+
441+
@mock.patch("ha_cluster_info.loader.get_pcsd_settings_conf")
442+
def test_permission_load_bad_format(
443+
self, mock_load_pcsd_permisions: mock.Mock
444+
) -> None:
445+
mock_load_pcsd_permisions.return_value = {
446+
"permissions": {
447+
"local": [
448+
{
449+
"type": "group",
450+
"name": "haclient",
451+
"allow": ["grant", "read", "write"],
452+
},
453+
]
454+
}
455+
}
456+
457+
with self.assertRaises(ha_cluster_info.exporter.JsonMissingKey) as cm:
458+
ha_cluster_info.export_pcsd_configuration()
459+
self.assertEqual(
460+
cm.exception.kwargs,
461+
dict(
462+
data=mock_load_pcsd_permisions.return_value,
463+
key="local_cluster",
464+
data_desc="pcs_settings.conf",
465+
),
466+
)

tests/unit/test_info_exporter.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,3 +540,87 @@ def test_pcs_nodes(self) -> None:
540540
),
541541
],
542542
)
543+
544+
545+
class ExportPcsPermissionList(TestCase):
546+
maxDiff = None
547+
548+
def test_minimal(self) -> None:
549+
pcs_settings_dict: Dict[str, Any] = {
550+
"permissions": {
551+
"local_cluster": [],
552+
}
553+
}
554+
555+
self.assertEqual(
556+
exporter.export_pcs_permission_list(pcs_settings_dict),
557+
[],
558+
)
559+
560+
def test_success(self) -> None:
561+
pcs_settings_dict: Dict[str, Any] = {
562+
"permissions": {
563+
"local_cluster": [
564+
{"name": "test1", "type": "user", "allow": []},
565+
{"name": "test2", "type": "user", "allow": ["read"]},
566+
{
567+
"name": "test3",
568+
"type": "group",
569+
"allow": ["write", "grant"],
570+
},
571+
]
572+
}
573+
}
574+
575+
self.assertEqual(
576+
exporter.export_pcs_permission_list(pcs_settings_dict),
577+
[
578+
{"name": "test1", "type": "user", "allow_list": []},
579+
{"name": "test2", "type": "user", "allow_list": ["read"]},
580+
{
581+
"name": "test3",
582+
"type": "group",
583+
"allow_list": ["write", "grant"],
584+
},
585+
],
586+
)
587+
588+
def assert_missing_key(
589+
self, pcs_settings_dict: Dict[str, Any], key: str
590+
) -> None:
591+
with self.assertRaises(exporter.JsonMissingKey) as cm:
592+
exporter.export_pcs_permission_list(pcs_settings_dict)
593+
self.assertEqual(
594+
cm.exception.kwargs,
595+
dict(
596+
data=pcs_settings_dict,
597+
key=key,
598+
data_desc="pcs_settings.conf",
599+
),
600+
)
601+
602+
def test_missing_key(self) -> None:
603+
self.assert_missing_key(
604+
dict(),
605+
"permissions",
606+
)
607+
self.assert_missing_key(
608+
dict(permissions=dict()),
609+
"local_cluster",
610+
)
611+
self.assert_missing_key(
612+
dict(permissions=dict(local_cluster=[dict()])),
613+
"type",
614+
)
615+
self.assert_missing_key(
616+
dict(permissions=dict(local_cluster=[dict(type="user")])),
617+
"name",
618+
)
619+
self.assert_missing_key(
620+
dict(
621+
permissions=dict(
622+
local_cluster=[dict(type="user", name="user1")]
623+
)
624+
),
625+
"allow",
626+
)

0 commit comments

Comments
 (0)