Skip to content

Commit 4953088

Browse files
authored
Merge pull request #13445 from jan-cerny/json_in_build
Speed up build by using JSON for interim atifacts
2 parents 410d87d + b507749 commit 4953088

22 files changed

+236
-143
lines changed

build-scripts/build_tests.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python3
22

33
import argparse
4+
import json
45
import logging
56
import os
67
import pathlib
@@ -179,9 +180,10 @@ def _process_rules(env_yaml: Dict, output_path: pathlib.Path,
179180
resolved_root: pathlib.Path) -> None:
180181
product = resolved_root.parent.name
181182
for rule_id in product_rules:
182-
rule_file = resolved_root / f'{rule_id}.yml'
183+
rule_file = resolved_root / f'{rule_id}.json'
183184

184-
rendered_rule_obj = ssg.yaml.open_raw(str(rule_file))
185+
with open(rule_file, "r") as file:
186+
rendered_rule_obj = json.load(file)
185187
rule_path = pathlib.Path(rendered_rule_obj["definition_location"])
186188
rule_root = rule_path.parent
187189

@@ -197,7 +199,8 @@ def _get_rules_in_profile(built_profiles_root) -> Generator[str, None, None]:
197199
for profile_file in built_profiles_root.iterdir(): # type: pathlib.Path
198200
if not profile_file.name.endswith(".profile"):
199201
continue
200-
profile_data = ssg.yaml.open_raw(str(profile_file.absolute()))
202+
with open(str(profile_file.absolute()), "r") as file:
203+
profile_data = json.load(file)
201204
for selection in profile_data["selections"]:
202205
if "=" not in selection:
203206
yield selection

build-scripts/collect_remediations.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ def main():
135135
for platform_file in os.listdir(args.platforms_dir):
136136
platform_path = os.path.join(args.platforms_dir, platform_file)
137137
try:
138-
platform = ssg.build_yaml.Platform.from_yaml(platform_path, env_yaml, product_cpes)
138+
platform = ssg.build_yaml.Platform.from_compiled_json(
139+
platform_path, env_yaml, product_cpes)
139140
except ssg.build_yaml.DocumentationNotComplete:
140141
# Happens on non-debug build when a platform is
141142
# "documentation-incomplete"
@@ -145,7 +146,7 @@ def main():
145146
for rule_file in os.listdir(args.resolved_rules_dir):
146147
rule_path = os.path.join(args.resolved_rules_dir, rule_file)
147148
try:
148-
rule = ssg.build_yaml.Rule.from_yaml(rule_path, env_yaml)
149+
rule = ssg.build_yaml.Rule.from_compiled_json(rule_path, env_yaml)
149150
except ssg.build_yaml.DocumentationNotComplete:
150151
# Happens on non-debug build when a rule is
151152
# "documentation-incomplete"

build-scripts/compile_all.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def load_benchmark_source_data_from_directory_tree(loader, env_yaml, product_yam
9191

9292
def dump_compiled_profile(base_dir, profile):
9393
dest = os.path.join(base_dir, "profiles", "{name}.profile".format(name=profile.id_))
94-
profile.dump_yaml(dest)
94+
profile.dump_json(dest)
9595

9696

9797
def get_all_resolved_profiles_by_id(
@@ -121,6 +121,8 @@ def load_resolve_and_validate_profiles(
121121
def save_everything(base_dir, loader, controls_manager, profiles):
122122
controls_manager.save_everything(os.path.join(base_dir, "controls"))
123123
loader.save_all_entities(base_dir)
124+
if not os.path.exists(os.path.join(base_dir, "profiles")):
125+
os.makedirs(os.path.join(base_dir, "profiles"))
124126
for p in profiles:
125127
dump_compiled_profile(base_dir, p)
126128

build-scripts/generate_manifest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def create_parser():
3636

3737
def add_rule_attributes(main_dir, output_dict, rule_id):
3838
rule_filename = os.path.join(main_dir, "rules", rule_id + ".yml")
39-
r = build_yaml.Rule.from_yaml(rule_filename)
39+
r = build_yaml.Rule.from_compiled_json(rule_filename)
4040
platform_names = r.cpe_platform_names.union(r.inherited_cpe_platform_names)
4141
output_dict["platform_names"] = sorted(list(platform_names))
4242

@@ -64,7 +64,7 @@ def add_profile_data(output_dict, main_dir):
6464
profiles_glob = os.path.join(main_dir, "profiles", "*.profile")
6565
filenames = glob(profiles_glob)
6666
for path in sorted(filenames):
67-
p = profile.Profile.from_yaml(path)
67+
p = profile.Profile.from_compiled_json(path)
6868
output_dict[p.id_] = dict()
6969
output_dict[p.id_]["rules"] = sorted(p.selected)
7070

ssg/build_cpe.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,17 @@ def load_cpes_from_directory_tree(self, root_path, env_yaml):
7979
continue
8080

8181
_, ext = os.path.splitext(os.path.basename(dir_item_path))
82-
if ext != '.yml':
82+
if ext == ".yml":
83+
cpe_item = CPEItem.from_yaml(dir_item_path, env_yaml)
84+
elif ext == ".json":
85+
cpe_item = CPEItem.from_compiled_json(dir_item_path, env_yaml)
86+
else:
8387
sys.stderr.write(
8488
"Encountered file '%s' while looking for content CPEs, "
8589
"extension '%s' is unknown. Skipping..\n"
8690
% (dir_item, ext)
8791
)
8892
continue
89-
90-
cpe_item = CPEItem.from_yaml(dir_item_path, env_yaml)
9193
self.add_cpe_item(cpe_item)
9294

9395
def add_cpe_item(self, cpe_item):

ssg/build_yaml.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,9 @@ def process_input_dict(cls, input_contents, env_yaml, product_cpes=None):
330330
Raises:
331331
ValueError: If the operator in the input data is not one of the expected possible operators.
332332
"""
333-
input_contents["interactive"] = (
334-
input_contents.get("interactive", "false").lower() == "true")
333+
if "interactive" in input_contents and isinstance(input_contents["interactive"], str):
334+
input_contents["interactive"] = (
335+
input_contents.get("interactive", "false").lower() == "true")
335336

336337
data = super(Value, cls).process_input_dict(input_contents, env_yaml)
337338

@@ -578,7 +579,7 @@ def add_profiles_from_dir(self, dir_, env_yaml, product_cpes):
578579
continue
579580

580581
try:
581-
new_profile = ProfileWithInlinePolicies.from_yaml(
582+
new_profile = ProfileWithInlinePolicies.from_compiled_json(
582583
dir_item_path, env_yaml, product_cpes)
583584
except DocumentationNotComplete:
584585
continue
@@ -2867,9 +2868,9 @@ def save_entities(self, entities, destdir):
28672868
if not entities:
28682869
return
28692870
for entity in entities:
2870-
basename = entity.id_ + ".yml"
2871+
basename = entity.id_ + ".json"
28712872
dest_filename = os.path.join(destdir, basename)
2872-
entity.dump_yaml(dest_filename)
2873+
entity.dump_json(dest_filename)
28732874

28742875

28752876
class BuildLoader(DirectoryLoader):
@@ -3097,18 +3098,18 @@ def find_first_groups_ids(self, start_dir):
30973098

30983099
def load_entities_by_id(self, filenames, destination, cls):
30993100
"""
3100-
Loads entities from a list of YAML files and stores them in a destination dictionary by their ID.
3101+
Loads entities from a list of JSON files and stores them in a destination dictionary by their ID.
31013102
31023103
Args:
3103-
filenames (list of str): List of file paths to YAML files.
3104+
filenames (list of str): List of file paths to JSON files.
31043105
destination (dict): Dictionary to store the loaded entities, keyed by their ID.
3105-
cls (type): Class type that has a `from_yaml` method to create an instance from a YAML file.
3106+
cls (type): Class type that has a `from_yaml` method to create an instance from a JSON file.
31063107
31073108
Returns:
31083109
None
31093110
"""
31103111
for fname in filenames:
3111-
entity = cls.from_yaml(fname, self.env_yaml, self.product_cpes)
3112+
entity = cls.from_compiled_json(fname, self.env_yaml, self.product_cpes)
31123113
destination[entity.id_] = entity
31133114

31143115
def add_fixes_to_rules(self):
@@ -3187,7 +3188,7 @@ def get_benchmark_xml_by_profile(self, rule_and_variables_dict):
31873188
)
31883189

31893190
for profile in self.benchmark.profiles:
3190-
if profile.single_rule_profile == "true":
3191+
if profile.single_rule_profile:
31913192
profiles_ids, benchmark = self.benchmark.get_benchmark_xml_for_profiles(
31923193
self.env_yaml, [profile], rule_and_variables_dict, include_contributors=False
31933194
)
@@ -3225,16 +3226,16 @@ def load_compiled_content(self):
32253226

32263227
self.fixes = ssg.build_remediations.load_compiled_remediations(self.fixes_dir)
32273228

3228-
filenames = glob.glob(os.path.join(self.resolved_rules_dir, "*.yml"))
3229+
filenames = glob.glob(os.path.join(self.resolved_rules_dir, "*.json"))
32293230
self.load_entities_by_id(filenames, self.rules, Rule)
32303231

3231-
filenames = glob.glob(os.path.join(self.resolved_groups_dir, "*.yml"))
3232+
filenames = glob.glob(os.path.join(self.resolved_groups_dir, "*.json"))
32323233
self.load_entities_by_id(filenames, self.groups, Group)
32333234

3234-
filenames = glob.glob(os.path.join(self.resolved_values_dir, "*.yml"))
3235+
filenames = glob.glob(os.path.join(self.resolved_values_dir, "*.json"))
32353236
self.load_entities_by_id(filenames, self.values, Value)
32363237

3237-
filenames = glob.glob(os.path.join(self.resolved_platforms_dir, "*.yml"))
3238+
filenames = glob.glob(os.path.join(self.resolved_platforms_dir, "*.json"))
32383239
self.load_entities_by_id(filenames, self.platforms, Platform)
32393240
self.product_cpes.platforms = self.platforms
32403241

@@ -3253,7 +3254,7 @@ def export_benchmark_to_xml(self, rule_and_variables_dict, ignore_single_rule_pr
32533254
str: The benchmark data in XML format.
32543255
"""
32553256
if ignore_single_rule_profiles:
3256-
profiles = [p for p in self.benchmark.profiles if p.single_rule_profile == "false"]
3257+
profiles = [p for p in self.benchmark.profiles if not p.single_rule_profile]
32573258
else:
32583259
profiles = self.benchmark.profiles
32593260
_, benchmark = self.benchmark.get_benchmark_xml_for_profiles(
@@ -3535,12 +3536,12 @@ def from_yaml(cls, yaml_file, env_yaml=None, product_cpes=None):
35353536
Platform: A Platform object created from the YAML file.
35363537
"""
35373538
platform = super(Platform, cls).from_yaml(yaml_file, env_yaml)
3538-
# If we received a product_cpes, we can restore also the original test object
3539-
# it can be later used e.g. for comparison
3540-
if product_cpes:
3541-
platform.test = product_cpes.algebra.parse(platform.original_expression, simplify=True)
3542-
product_cpes.add_resolved_cpe_items_from_platform(platform)
3543-
return platform
3539+
return restore_original_test_object(platform, product_cpes)
3540+
3541+
@classmethod
3542+
def from_compiled_json(cls, json_file_path, env_yaml=None, product_cpes=None):
3543+
platform = super(Platform, cls).from_compiled_json(json_file_path, env_yaml)
3544+
return restore_original_test_object(platform, product_cpes)
35443545

35453546
def get_fact_refs(self):
35463547
"""
@@ -3602,3 +3603,12 @@ def add_platform_if_not_defined(platform, product_cpes):
36023603
return p
36033604
product_cpes.platforms[platform.id_] = platform
36043605
return platform
3606+
3607+
3608+
def restore_original_test_object(platform, product_cpes):
3609+
# If we received a product_cpes, we can restore also the original test object
3610+
# it can be later used e.g. for comparison
3611+
if product_cpes:
3612+
platform.test = product_cpes.algebra.parse(platform.original_expression, simplify=True)
3613+
product_cpes.add_resolved_cpe_items_from_platform(platform)
3614+
return platform

ssg/controls.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,17 @@ def __init__(self, controls_dir, env_yaml=None, existing_rules=None):
810810
self.existing_rules = existing_rules
811811
self.policies = {}
812812

813+
def _load(self, format):
814+
if not os.path.exists(self.controls_dir):
815+
return
816+
for filename in sorted(glob(os.path.join(self.controls_dir, "*." + format))):
817+
filepath = os.path.join(self.controls_dir, filename)
818+
policy = Policy(filepath, self.env_yaml)
819+
policy.load()
820+
self.policies[policy.id] = policy
821+
self.check_all_rules_exist()
822+
self.resolve_controls()
823+
813824
def load(self):
814825
"""
815826
Load policies from YAML files in the controls directory.
@@ -822,15 +833,10 @@ def load(self):
822833
Returns:
823834
None
824835
"""
825-
if not os.path.exists(self.controls_dir):
826-
return
827-
for filename in sorted(glob(os.path.join(self.controls_dir, "*.yml"))):
828-
filepath = os.path.join(self.controls_dir, filename)
829-
policy = Policy(filepath, self.env_yaml)
830-
policy.load()
831-
self.policies[policy.id] = policy
832-
self.check_all_rules_exist()
833-
self.resolve_controls()
836+
self._load("yml")
837+
838+
def load_compiled(self):
839+
self._load("json")
834840

835841
def check_all_rules_exist(self):
836842
"""
@@ -1050,7 +1056,7 @@ def get_all_controls(self, policy_id):
10501056

10511057
def save_everything(self, output_dir):
10521058
"""
1053-
Saves all policies to the specified output directory in YAML format.
1059+
Saves all policies to the specified output directory in JSON format.
10541060
10551061
Args:
10561062
output_dir (str): The directory where the policy files will be saved.
@@ -1060,8 +1066,8 @@ def save_everything(self, output_dir):
10601066
"""
10611067
ssg.utils.mkdir_p(output_dir)
10621068
for policy_id, policy in self.policies.items():
1063-
filename = os.path.join(output_dir, "{}.{}".format(policy_id, "yml"))
1064-
policy.dump_yaml(filename)
1069+
filename = os.path.join(output_dir, "{}.{}".format(policy_id, "json"))
1070+
policy.dump_json(filename)
10651071

10661072
def add_references(self, rules):
10671073
"""

0 commit comments

Comments
 (0)