Skip to content

Commit

Permalink
Merge pull request ComplianceAsCode#12001 from Honny1/show-most-used-…
Browse files Browse the repository at this point in the history
…rules-of-component

Show most used rules of component
  • Loading branch information
jan-cerny authored May 27, 2024
2 parents a7d29a7 + 95be94a commit 1994cfb
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 10 deletions.
9 changes: 9 additions & 0 deletions build-scripts/profile_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,15 @@ def parse_most_used_components(subparsers):
choices=get_available_products_with_components_root(),
default=get_available_products_with_components_root(),
)
parser_most_used_components.add_argument(
"--used-rules",
default=False,
action="store_true",
help=(
"For every component, show the usage of each component's rule "
"in the profiles in given product."
),
)


def get_available_products_with_components_root():
Expand Down
7 changes: 7 additions & 0 deletions docs/manual/developer/05_tools_and_utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ Optionally, you can use this command to limit the statistics for a specific prod
$ ./build-scripts/profile_tool.py most-used-components --products rhel9
```

You can also get a list of the most used components with used rules for the RHEL9 product, you can use the `--used-rules` flag.
As shown in this command:

```bash
$ ./build-scripts/profile_tool.py most-used-components --products rhel9 --used-rules
```

The result will be a list of rules with the number of uses in the profiles.
The list can be generated as plain text, JSON or CVS.
Via the `--format FORMAT` parameter.
Expand Down
16 changes: 16 additions & 0 deletions utils/profile_tool/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,19 @@ def generate_output(dict_, format, csv_header):

for rule_id, rule_count in dict_.items():
print(f_string.format(rule_id, rule_count))


def _format_value_b(value_b, delim):
str_ = ""
if len(value_b) != 0:
values = ", ".join([f"{key}: {value}" for key, value in value_b.items()])
str_ = f"{delim}{values}"
return str_


def merge_dicts(dict_a, dict_b, delim):
out = {}
for key, value in dict_a.items():
value_b = dict_b.get(key, {})
out[key] = str(value) + _format_value_b(value_b, delim)
return out
41 changes: 35 additions & 6 deletions utils/profile_tool/most_used_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import ssg.components

from .most_used_rules import _sorted_dict_by_num_value
from .common import generate_output
from .common import generate_output, merge_dicts

PYTHON_2 = sys.version_info[0] < 3

Expand All @@ -17,10 +17,21 @@
)


def _count_components(components, rules_list, components_out):
def _count_rules_components(component_name, rules, used_rules_of_components_out):
used_rules = defaultdict(int)
if component_name in used_rules_of_components_out:
used_rules = used_rules_of_components_out[component_name]
for rule in rules:
used_rules[rule] += 1
used_rules_of_components_out[component_name] = used_rules


def _count_components(components, rules_list, components_out, used_rules_of_components_out):
for component_name, component in components.items():
if len(set(component.rules).intersection(set(rules_list))) > 0:
intersection = set(component.rules).intersection(set(rules_list))
if len(intersection) > 0:
components_out[component_name] += 1
_count_rules_components(component_name, intersection, used_rules_of_components_out)


def load_components(product):
Expand All @@ -33,7 +44,7 @@ def load_components(product):
return ssg.components.load(components_dir)


def _process_all_products_from_controls(components_out, products):
def _process_all_products_from_controls(components_out, used_rules_of_components_out, products):
if PYTHON_2:
raise Exception("This feature is not supported for python2.")

Expand All @@ -43,14 +54,32 @@ def _process_all_products_from_controls(components_out, products):
continue
controls_manager = load_controls_manager("./controls/", product)
for profile in _get_profiles_for_product(controls_manager, product):
_count_components(components, profile.rules, components_out)
_count_components(
components, profile.rules, components_out, used_rules_of_components_out
)


def _sort_rules_of_components(used_rules_of_components):
out = {}
for key, value in used_rules_of_components.items():
out[key] = _sorted_dict_by_num_value(value)
return out


def command_most_used_components(args):
components = defaultdict(int)
used_rules_of_components = {}

_process_all_products_from_controls(components, args.products)
_process_all_products_from_controls(components, used_rules_of_components, args.products)

sorted_components = _sorted_dict_by_num_value(components)
csv_header = "component_name,count_of_profiles"
if args.used_rules:
csv_header = "component_name,count_of_profiles,used_rules:count_of_profiles"
delim = " "
if args.format == "csv":
delim = ","
sorted_used_rules_of_components = _sort_rules_of_components(used_rules_of_components)
sorted_components = merge_dicts(sorted_components, sorted_used_rules_of_components, delim)

generate_output(sorted_components, args.format, csv_header)
3 changes: 2 additions & 1 deletion utils/profile_tool/most_used_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def _get_profiles_for_product(ctrls_mgr, product):

profiles = []
for file in profiles_files:
profiles.append(get_profile(profiles_files, file, ctrls_mgr.policies))
if "default.profile" not in file:
profiles.append(get_profile(profiles_files, file, ctrls_mgr.policies))
return profiles


Expand Down
10 changes: 7 additions & 3 deletions utils/profile_tool/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,14 @@ def _get_sel(selected):
return policy, control, level

@staticmethod
def _get_levels(policy, level):
levels = [level]
def _get_levels(policy, level, levels=None):
if levels is None:
levels = set()
levels.add(level)

if policy.levels_by_id.get(level).inherits_from is not None:
levels.extend(policy.levels_by_id.get(level).inherits_from)
for parent_level in policy.levels_by_id.get(level).inherits_from:
levels.update(Profile._get_levels(policy, parent_level, levels))
return levels

def add_from_policy(self, policies, selected):
Expand Down

0 comments on commit 1994cfb

Please sign in to comment.