Skip to content

Commit

Permalink
Merge branch 'main' into shutdown_with_options
Browse files Browse the repository at this point in the history
  • Loading branch information
mkanoor authored Feb 3, 2023
2 parents 13b9a8a + 31bae65 commit 0d426c7
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 6 deletions.
3 changes: 3 additions & 0 deletions ansible_rulebook/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ async def debug(event_log, **kwargs):
print("kwargs:")
pprint(kwargs)
print(get_horizontal_rule("="))
print("facts:")
pprint(lang.get_facts(kwargs["source_ruleset_name"]))
print(get_horizontal_rule("="))
sys.stdout.flush()
await event_log.put(
dict(
Expand Down
2 changes: 1 addition & 1 deletion ansible_rulebook/condition_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ def OperatorExpressionFactory(tokens):
condition = infix_notation(
all_terms,
[
("not", 1, OpAssoc.RIGHT, lambda toks: NegateExpression(*toks[0])),
(
one_of("* /"),
2,
Expand Down Expand Up @@ -208,6 +207,7 @@ def OperatorExpressionFactory(tokens):
OpAssoc.LEFT,
lambda toks: OperatorExpressionFactory(toks[0]),
),
("not", 1, OpAssoc.RIGHT, lambda toks: NegateExpression(*toks[0])),
(
one_of(["and", "or"]),
2,
Expand Down
5 changes: 5 additions & 0 deletions ansible_rulebook/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,8 @@ class ControllerApiException(Exception):
class VarsKeyMissingException(Exception):

pass


class InvalidAssignmentException(Exception):

pass
27 changes: 26 additions & 1 deletion ansible_rulebook/json_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
SearchType,
String,
)
from ansible_rulebook.exception import VarsKeyMissingException
from ansible_rulebook.exception import (
InvalidAssignmentException,
VarsKeyMissingException,
)
from ansible_rulebook.rule_types import (
Action,
Condition as RuleCondition,
Expand Down Expand Up @@ -119,6 +122,9 @@ def visit_condition(parsed_condition: ConditionTypes, variables: Dict):
value=visit_condition(parsed_condition.value, variables),
)
elif isinstance(parsed_condition, OperatorExpression):
if parsed_condition.operator == "<<":
validate_assignment_expression(parsed_condition.left.value)

if parsed_condition.operator in OPERATOR_MNEMONIC:
return create_binary_node(
OPERATOR_MNEMONIC[parsed_condition.operator],
Expand Down Expand Up @@ -281,3 +287,22 @@ def visit_ruleset(ruleset: RuleSet, variables: Dict):
def generate_dict_rulesets(ruleset: List[RuleSet], variables: Dict):
"""Generate JSON compatible rulesets."""
return [visit_ruleset(ruleset, variables) for ruleset in ruleset]


def validate_assignment_expression(value):
tokens = value.split(".")
if len(tokens) != 2:
msg = (
f"Assignment variable: {value} is invalid."
+ "Valid values start with events or facts. e.g events.var1 "
+ "or facts.var1 "
+ "Where var1 can only contain alpha numeric and _ charachters"
)
raise InvalidAssignmentException(msg)

if tokens[0] not in ["events", "facts"]:
msg = (
"Only events and facts can be used in assignment. "
+ f"{value} is invalid."
)
raise InvalidAssignmentException(msg)
4 changes: 2 additions & 2 deletions docs/conditions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,9 @@ Negation Example
| In this example the boolean expression is evaluated first and then negated.
.. note::
``not`` operator can work without parenthesis when the value is a boolean, E.g ``condition: not event.disabled``
``not`` operator can work without parenthesis when the value is a single logical statement

In the rest of the cases the expression must be enclosed in parenthesis. E.g ``condition: not (event.i == 4)``
If there are multiple logical statements with **or** or **and** please use round brackets like shown above.


Adding time constraints for rules with multiple conditions
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,5 @@ def test_actions_sanity():
assert "Echo action executed" in result.stdout

assert (
len(result.stdout.splitlines()) == 42
len(result.stdout.splitlines()) == 45
), "unexpected output from the rulebook"
10 changes: 10 additions & 0 deletions tests/examples/50_negation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- b: false
- bt: true
- i: 10
- msg: Fred
- j: 9
rules:
- name: r1
condition: not event.b
Expand All @@ -21,3 +23,11 @@
condition: not (event.i > 50 or event.i < 10)
action:
print_event:
- name: r4
condition: not event.msg == "Barney"
action:
print_event:
- name: r5
condition: not event.j >= 10
action:
print_event:
16 changes: 16 additions & 0 deletions tests/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import yaml

from ansible_rulebook.condition_parser import parse_condition
from ansible_rulebook.exception import InvalidAssignmentException
from ansible_rulebook.json_generator import (
generate_dict_rulesets,
visit_condition,
Expand Down Expand Up @@ -418,3 +419,18 @@ def test_generate_dict_ruleset(rulebook):
ast = yaml.safe_load(f.read())

assert ruleset == ast


def test_invalid_assignment_operator():
with pytest.raises(InvalidAssignmentException):
visit_condition(
parse_condition("event.first << fact.range.i == 'Hello'"), {}
)


def test_invalid_nested_assignment_operator():
with pytest.raises(InvalidAssignmentException):
visit_condition(
parse_condition("events.first.abc.xyz << fact.range.i == 'Hello'"),
{},
)
10 changes: 9 additions & 1 deletion tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -1248,7 +1248,15 @@ async def test_50_negation():
assert event["action"] == "print_event", "5"
assert event["matching_events"] == {"m": {"i": 10}}, "5"
event = event_log.get_nowait()
assert event["type"] == "Shutdown", "7"
assert event["type"] == "Action", "6"
assert event["action"] == "print_event", "6"
assert event["matching_events"] == {"m": {"msg": "Fred"}}, "6"
event = event_log.get_nowait()
assert event["type"] == "Action", "7"
assert event["action"] == "print_event", "7"
assert event["matching_events"] == {"m": {"j": 9}}, "7"
event = event_log.get_nowait()
assert event["type"] == "Shutdown", "8"


@pytest.mark.asyncio
Expand Down

0 comments on commit 0d426c7

Please sign in to comment.