Skip to content

Commit 5e64a4a

Browse files
authored
feat: adds check to TrestleRule to match compliance-trestle CSV fields (#173)
Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
1 parent 8e236d3 commit 5e64a4a

9 files changed

Lines changed: 55 additions & 2 deletions

File tree

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
)
2929
from trestlebot import const
3030
from trestlebot.transformers.trestle_rule import (
31+
Check,
3132
ComponentInfo,
3233
Control,
3334
Parameter,
@@ -168,6 +169,7 @@ def test_rule() -> TrestleRule:
168169
alternative_values={},
169170
default_value="test",
170171
),
172+
check=Check(name="test_check", description="test check"),
171173
profile=Profile(
172174
description="test", href="test", include_controls=[Control(id="ac-1")]
173175
),

tests/data/yaml/test_complete_rule.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ x-trestle-rule-info:
66
description: prm_1 description
77
alternative-values: {'default': '5%', '5pc': '5%', '10pc': '10%', '15pc': '15%', '20pc': '20%'}
88
default-value: '5%'
9+
check:
10+
name: my_check
11+
description: My check description
912
profile:
1013
description: Simple NIST Profile
1114
href: profiles/simplified_nist_profile/profile.json

tests/trestlebot/tasks/test_rule_transform_task.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_rule_transform_task(tmp_trestle_dir: str) -> None:
6262

6363
assert component is not None
6464
assert component.props is not None
65-
assert len(component.props) == 5
65+
assert len(component.props) == 7
6666
assert component.props[0].name == RULE_ID
6767
assert component.props[0].value == "example_rule_1"
6868
assert component.props[1].name == RULE_DESCRIPTION

tests/trestlebot/transformers/test_csv_transformer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def test_csv_builder(test_rule: TrestleRule, tmp_trestle_dir: str) -> None:
3838
assert row["Parameter_Value_Default"] == test_rule.parameter.default_value # type: ignore
3939
assert row["Profile_Description"] == test_rule.profile.description
4040
assert row["Profile_Source"] == test_rule.profile.href
41+
assert row["Check_Id"] == test_rule.check.name # type: ignore
42+
assert row["Check_Description"] == test_rule.check.description # type: ignore
4143

4244
trestle_root = pathlib.Path(tmp_trestle_dir)
4345
tmp_csv_path = trestle_root.joinpath("test.csv")

tests/trestlebot/transformers/test_yaml_transformer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ def test_rule_transformer() -> None:
4646
assert rule.parameter.default_value == "5%"
4747
assert rule.profile.description == "Simple NIST Profile"
4848
assert rule.profile.href == "profiles/simplified_nist_profile/profile.json"
49+
assert rule.check is not None
50+
assert rule.check.name == "my_check"
51+
assert rule.check.description == "My check description"
4952

5053

5154
def test_rules_transform_with_incomplete_rule() -> None:

trestlebot/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
NAME = "name"
2626
DESCRIPTION = "description"
2727
PARAMETER = "parameter"
28+
CHECK = "check"
2829
PROFILE = "profile"
2930
HREF = "href"
3031
ALTERNATIVE_VALUES = "alternative-values"

trestlebot/transformers/csv_transformer.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import trestle.tasks.csv_to_oscal_cd as csv_to_oscal_cd
1414
from trestle.common.const import TRESTLE_GENERIC_NS
1515
from trestle.tasks.csv_to_oscal_cd import (
16+
CHECK_DESCRIPTION,
17+
CHECK_ID,
1618
COMPONENT_DESCRIPTION,
1719
COMPONENT_TITLE,
1820
COMPONENT_TYPE,
@@ -35,6 +37,7 @@
3537
ToRulesTransformer,
3638
)
3739
from trestlebot.transformers.trestle_rule import (
40+
Check,
3841
ComponentInfo,
3942
Control,
4043
Parameter,
@@ -65,13 +68,15 @@ def transform(self, row: Dict[str, str]) -> TrestleRule:
6568
profile = self._extract_profile(row)
6669
component_info = self._extract_component_info(row)
6770
parameter = self._extract_parameter(row)
71+
check = self._extract_check(row)
6872

6973
return TrestleRule(
7074
name=rule_info[const.NAME],
7175
description=rule_info[const.DESCRIPTION],
7276
component=component_info,
7377
parameter=parameter,
7478
profile=profile,
79+
check=check,
7580
)
7681

7782
def _extract_rule_info(self, row: Dict[str, str]) -> Dict[str, str]:
@@ -106,6 +111,16 @@ def _extract_parameter(self, row: Dict[str, str]) -> Optional[Parameter]:
106111
)
107112
return None
108113

114+
def _extract_check(self, row: Dict[str, str]) -> Optional[Check]:
115+
"""Extract check information from a CSV row."""
116+
check_name = row.get(csv_to_oscal_cd.CHECK_ID, None)
117+
if check_name:
118+
return Check(
119+
name=check_name,
120+
description=row.get(csv_to_oscal_cd.CHECK_DESCRIPTION, ""),
121+
)
122+
return None
123+
109124
def _extract_component_info(self, row: Dict[str, str]) -> ComponentInfo:
110125
"""Extract component information from a CSV row."""
111126
return ComponentInfo(
@@ -141,6 +156,12 @@ def transform(self, rule: TrestleRule) -> Dict[str, str]:
141156
}
142157
if rule.parameter is not None:
143158
merged_dict.update(self._add_parameter(rule.parameter))
159+
if rule.check is not None:
160+
check: Dict[str, str] = {
161+
CHECK_ID: rule.check.name,
162+
CHECK_DESCRIPTION: rule.check.description,
163+
}
164+
merged_dict.update(check)
144165
return merged_dict
145166

146167
def _add_profile(self, profile: Profile) -> Dict[str, str]:

trestlebot/transformers/trestle_rule.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,25 @@ class ComponentInfo(BaseModel):
4646
description: str
4747

4848

49+
class Check(BaseModel):
50+
"""Check dataclass."""
51+
52+
name: str
53+
description: str
54+
55+
class Config:
56+
allow_population_by_field_name = True
57+
58+
4959
class TrestleRule(BaseModel):
5060
"""TrestleRule dataclass."""
5161

5262
name: str
5363
description: str
5464
component: ComponentInfo
55-
parameter: Optional[Parameter]
5665
profile: Profile
66+
check: Optional[Check]
67+
parameter: Optional[Parameter]
5768

5869

5970
def get_default_rule() -> TrestleRule:

trestlebot/transformers/yaml_transformer.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ToRulesTransformer,
1919
)
2020
from trestlebot.transformers.trestle_rule import (
21+
Check,
2122
ComponentInfo,
2223
Parameter,
2324
Profile,
@@ -66,12 +67,17 @@ def transform(self, blob: str) -> TrestleRule:
6667
rule_info_data[const.PARAMETER]
6768
)
6869

70+
check_instance: Optional[Check] = None
71+
if const.CHECK in rule_info_data:
72+
check_instance = Check.parse_obj(rule_info_data[const.CHECK])
73+
6974
rule_info_instance: TrestleRule = TrestleRule(
7075
name=rule_info_data[const.NAME],
7176
description=rule_info_data[const.DESCRIPTION],
7277
component=component_info_instance,
7378
parameter=parameter_instance,
7479
profile=profile_info_instance,
80+
check=check_instance,
7581
)
7682

7783
except KeyError as e:
@@ -134,4 +140,8 @@ def _to_rule_info(rule: TrestleRule) -> Dict[str, Any]:
134140
rule_info[const.RULE_INFO_TAG][const.PARAMETER] = rule.parameter.dict(
135141
by_alias=True, exclude_unset=True
136142
)
143+
if rule.check is not None:
144+
rule_info[const.RULE_INFO_TAG][const.CHECK] = rule.check.dict(
145+
by_alias=True, exclude_unset=True
146+
)
137147
return rule_info

0 commit comments

Comments
 (0)