Skip to content

Commit

Permalink
- Simplify v3 Guard result json (#230)
Browse files Browse the repository at this point in the history
* Update to cfn-guard to 3.0.0
* Simplify access of common attributes
  • Loading branch information
kddejong authored Sep 6, 2023
1 parent 3aac4e7 commit 2f5cc92
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 17 deletions.
2 changes: 1 addition & 1 deletion packages/cfn_guard_rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/cfn_guard_rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cfn_guard_rs"
version = "0.3.1"
version = "0.3.2"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
114 changes: 109 additions & 5 deletions packages/cfn_guard_rs/python/cfn_guard_rs/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@
"""
from __future__ import annotations

import abc
from dataclasses import dataclass, field
from typing import Any, List, Dict, Sequence, Tuple

# pylint: disable=missing-class-docstring,missing-function-docstring,invalid-name
class ValueComparisons(abc.ABC):
@property
@abc.abstractmethod
def value_from(self) -> Any:
pass

@property
@abc.abstractmethod
def value_to(self) -> Any:
pass


@dataclass(eq=True, frozen=True)
class Messages:
Expand All @@ -23,6 +35,13 @@ def from_object(cls, obj) -> "Messages":
error_message=obj.get("error_message"),
)

def __repr__(self) -> str:
if self.custom_message:
return self.custom_message
if self.error_message:
return self.error_message
return ""


@dataclass(eq=True, frozen=True)
class RuleReport:
Expand All @@ -47,7 +66,7 @@ def from_object(cls, obj) -> "RuleReport" | None:


@dataclass(eq=True, frozen=True)
class GuardBlockReport:
class GuardBlockReport(ValueComparisons):
"""Guard Block Report"""

context: str = field()
Expand All @@ -65,6 +84,14 @@ def from_object(cls, obj):
unresolved=obj.get("resolved"),
)

@property
def value_from(self) -> Any:
return None

@property
def value_to(self) -> Any:
return None


@dataclass(eq=True, frozen=True)
class DisjunctionsReport:
Expand All @@ -79,6 +106,7 @@ def from_object(cls, obj):

return cls(checks=ClauseReport.from_object(obj.get("checks")))


@dataclass(eq=True, frozen=True)
class UnaryComparison:
"""Unary Comparison"""
Expand Down Expand Up @@ -127,7 +155,7 @@ def from_object(cls, obj):


@dataclass(eq=True, frozen=True)
class UnaryCheck:
class UnaryCheck(ValueComparisons):
"""Unary Check"""

Resolved: UnaryComparison | None = field(default=None)
Expand All @@ -145,6 +173,18 @@ def from_object(cls, obj):
UnresolvedContext=obj.get("UnresolvedContext"),
)

@property
def value_from(self) -> Any:
if self.Resolved is not None:
return self.Resolved.value
if self.UnResolved is not None:
return self.UnResolved.traversed_to
return None

@property
def value_to(self) -> Any:
return None


@dataclass(eq=True, frozen=True)
class UnaryReport:
Expand Down Expand Up @@ -198,7 +238,7 @@ def from_object(cls, obj) -> "InComparison" | None:


@dataclass(eq=True, frozen=True)
class BinaryCheck:
class BinaryCheck(ValueComparisons):
Resolved: BinaryComparison | None = field(default=None)
UnResolved: UnResolved | None = field(default=None)
InResolved: Any = field(default=None)
Expand All @@ -211,6 +251,22 @@ def from_object(cls, obj):
InResolved=obj.get("InResolved"),
)

@property
def value_from(self) -> Any:
if self.Resolved is not None:
return self.Resolved.from_
if self.InResolved is not None:
return self.InResolved.from_
if self.UnResolved is not None:
return self.UnResolved.traversed_to
return None

@property
def value_to(self) -> Any:
if self.Resolved is not None:
return self.Resolved.to_
return None


@dataclass(eq=True, frozen=True)
class BinaryReport:
Expand All @@ -230,7 +286,7 @@ def from_object(cls, obj):


@dataclass(eq=True, frozen=True)
class GuardClauseReport:
class GuardClauseReport(ValueComparisons):
"""Guard Clause Report"""

Unary: UnaryReport | None = field(default=None)
Expand All @@ -246,9 +302,41 @@ def from_object(cls, obj):
Binary=BinaryReport.from_object(obj.get("Binary")),
)

@property
def context(self) -> str | None:
if self.Unary is not None:
return self.Unary.context
if self.Binary is not None:
return self.Binary.context
return None

@property
def messages(self) -> Messages | None:
if self.Unary is not None:
return self.Unary.messages
if self.Binary is not None:
return self.Binary.messages
return None

@property
def value_from(self):
if self.Unary is not None:
return self.Unary.check.value_from
if self.Binary is not None:
return self.Binary.check.value_from
return None

@property
def value_to(self):
if self.Unary is not None:
return self.Unary.check.value_to
if self.Binary is not None:
return self.Binary.check.value_to
return None


@dataclass(eq=True, frozen=True)
class ClauseReport:
class ClauseReport(ValueComparisons):
"""Clause Report"""

Rule: RuleReport | None = field(default=None)
Expand All @@ -273,6 +361,22 @@ def from_array(cls, items):

return results

@property
def value_from(self) -> Any:
if self.GuardBlock is not None:
return self.GuardBlock.value_from
if self.Clause is not None:
return self.Clause.value_from
return None

@property
def value_to(self) -> Any:
if self.GuardBlock is not None:
return self.GuardBlock.value_to
if self.Clause is not None:
return self.Clause.value_to
return None


# pylint: disable=too-many-instance-attributes
@dataclass(eq=True, frozen=True)
Expand Down
32 changes: 28 additions & 4 deletions packages/cfn_guard_rs/python/tests/test_cfn_guard_rs.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@


@pytest.mark.parametrize(
"template,rules,expected",
"template,rules,expected,values_from,values_to",
[
(
"python/tests/fixtures/templates/s3_bucket_name_valid.yaml",
Expand All @@ -38,6 +38,8 @@
not_applicable=[],
compliant=["default"],
),
[],
[],
),
(
"python/tests/fixtures/templates/s3_bucket_name_invalid.yaml",
Expand Down Expand Up @@ -89,6 +91,13 @@
not_applicable=[],
compliant=[],
),
[
{
"path": "/Resources/Bucket/Properties/BucketName",
"value": 1,
}
],
[None],
),
(
"python/tests/fixtures/templates/s3_bucket_public_access_valid.yaml",
Expand All @@ -103,6 +112,8 @@
not_applicable=[],
compliant=["S3_BUCKET_LEVEL_PUBLIC_ACCESS_PROHIBITED"],
),
[],
[],
),
(
"python/tests/fixtures/templates/s3_bucket_public_access_invalid.yaml",
Expand Down Expand Up @@ -163,20 +174,33 @@
not_applicable=[],
compliant=[],
),
[
{
"path": (
"/Resources/Bucket/Properties/"
"PublicAccessBlockConfiguration/BlockPublicAcls"
),
"value": "false",
}
],
[{"path": "", "value": "true"}],
),
],
)
def test_run_checks(template, rules, expected):
def test_run_checks(template, rules, expected, values_from, values_to):
"""Test transactions against run_checks"""
with open(template, encoding="utf8") as file:
template_str = yaml.safe_load(file)
with open(rules, encoding="utf8") as file:
rules = file.read()

result = run_checks(template_str, rules)
print(result)
print(expected)
assert result == expected
for i, value_from in enumerate(values_from):
assert result.not_compliant[0].Rule.checks[i].value_from == value_from

for i, value_to in enumerate(values_to):
assert result.not_compliant[i].Rule.checks[i].value_to == value_to


@pytest.mark.parametrize(
Expand Down
10 changes: 5 additions & 5 deletions packages/cfn_guard_rs_hook/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/cfn_guard_rs_hook/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = ["Kevin DeJong <[email protected]>"]
[tool.poetry.dependencies]
python = "^3.7.2"
cloudformation-cli-python-lib = "^2.1.12"
cfn-guard-rs = "^0.2.1"
cfn-guard-rs = "~0.2.3"
pyyaml = "~6.0.1"
Jinja2 = "^3.0.0"
jsonpath-rw = "^1.0.0"
Expand Down

0 comments on commit 2f5cc92

Please sign in to comment.