Skip to content

Commit

Permalink
Refactor actions
Browse files Browse the repository at this point in the history
Each action is a separate class
Allows for us to share code between actions
  • Loading branch information
mkanoor committed Jun 12, 2023
1 parent 26e6dd3 commit 51d3968
Show file tree
Hide file tree
Showing 33 changed files with 2,363 additions and 982 deletions.
Empty file.
89 changes: 89 additions & 0 deletions ansible_rulebook/action/base_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import uuid
from typing import Dict

from ansible_rulebook.conf import settings
from ansible_rulebook.event_filter.insert_meta_info import main as insert_meta
from ansible_rulebook.util import run_at

from .control import Control
from .metadata import Metadata

KEY_EDA_VARS = "ansible_eda"
INTERNAL_ACTION_STATUS = "successful"


class BaseAction:
def __init__(self, metadata: Metadata, control: Control, action: str):
self.metadata = metadata
self.control = control
self.uuid = str(uuid.uuid4())
self.action = action

async def send(self, data: dict, objtype: str = "Action") -> None:
payload = {
"type": objtype,
"action": self.action,
"action_uuid": self.uuid,
"ruleset": self.metadata.rule_set,
"ruleset_uuid": self.metadata.rule_set_uuid,
"rule": self.metadata.rule,
"rule_uuid": self.metadata.rule_uuid,
"rule_run_at": self.metadata.rule_run_at,
"activation_id": settings.identifier,
}
payload.update(data)
await self.control.event_log.put(payload)

async def send_default_status(self):
await self.send(
{
"run_at": run_at(),
"status": INTERNAL_ACTION_STATUS,
"matching_events": self._get_events(),
}
)

def _get_events(self) -> Dict:
if "event" in self.control.variables:
return {"m": self.control.variables["event"]}
if "events" in self.control.variables:
return self.control.variables["events"]
return {}

def _embellish_internal_event(self, event: Dict) -> Dict:
return insert_meta(
event, **{"source_name": self.action, "source_type": "internal"}
)

def set_action(self, action):
self.action = action

def _collect_extra_vars(self, user_extra_vars: dict) -> dict:
extra_vars = user_extra_vars.copy() if user_extra_vars else {}

eda_vars = {
"ruleset": self.metadata.rule_set,
"rule": self.metadata.rule,
}
if "events" in self.control.variables:
eda_vars["events"] = self.control.variables["events"]
if "event" in self.control.variables:
eda_vars["event"] = self.control.variables["event"]

extra_vars[KEY_EDA_VARS] = eda_vars
return extra_vars
33 changes: 33 additions & 0 deletions ansible_rulebook/action/control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import asyncio
from dataclasses import dataclass
from typing import List


@dataclass(frozen=True)
class Control:
__slots__ = [
"event_log",
"inventory",
"hosts",
"variables",
"project_data_file",
]
event_log: asyncio.Queue
inventory: str
hosts: List[str]
variables: dict
project_data_file: str
71 changes: 71 additions & 0 deletions ansible_rulebook/action/debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import sys
from dataclasses import asdict
from pprint import pprint

import dpath
from drools import ruleset as lang

from ansible_rulebook.util import get_horizontal_rule

from .base_action import BaseAction
from .control import Control
from .metadata import Metadata

logger = logging.getLogger(__name__)


class Debug(BaseAction):
def __init__(self, metadata: Metadata, control: Control, **action_args):
super().__init__(metadata, control, "debug")
self.action_args = action_args

async def __call__(self):

if "msg" in self.action_args:
messages = self.action_args.get("msg")
if not isinstance(messages, list):
messages = [messages]
for msg in messages:
print(msg)
elif "var" in self.action_args:
key = self.action_args.get("var")
try:
print(dpath.get(self.control.variables, key, separator="."))
except KeyError:
logger.error("Key %s not found in variable pool", key)
raise
else:
print(get_horizontal_rule("="))
print("kwargs:")
args = asdict(self.metadata)
args.update(
{
"inventory": self.control.inventory,
"hosts": self.control.hosts,
"variables": self.control.variables,
"project_data_file": self.control.project_data_file,
}
)
pprint(args)
print(get_horizontal_rule("="))
print("facts:")
pprint(lang.get_facts(self.metadata.rule_set))
print(get_horizontal_rule("="))

sys.stdout.flush()
await self.send_default_status()
31 changes: 31 additions & 0 deletions ansible_rulebook/action/metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from dataclasses import dataclass


@dataclass(frozen=True)
class Metadata:
__slots__ = [
"rule",
"rule_uuid",
"rule_set",
"rule_set_uuid",
"rule_run_at",
]
rule: str
rule_uuid: str
rule_set: str
rule_set_uuid: str
rule_run_at: str
30 changes: 30 additions & 0 deletions ansible_rulebook/action/noop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging

from .base_action import BaseAction
from .control import Control
from .metadata import Metadata

logger = logging.getLogger(__name__)


class Noop(BaseAction):
def __init__(self, metadata: Metadata, control: Control, **action_args):
super().__init__(metadata, control, "noop")
self.action_args = action_args

async def __call__(self):
await self.send_default_status()
36 changes: 36 additions & 0 deletions ansible_rulebook/action/post_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging

from drools import ruleset as lang

from .base_action import BaseAction
from .control import Control
from .metadata import Metadata

logger = logging.getLogger(__name__)


class PostEvent(BaseAction):
def __init__(self, metadata: Metadata, control: Control, **action_args):
super().__init__(metadata, control, "post_event")
self.action_args = action_args

async def __call__(self):
lang.post(
self.action_args["ruleset"],
self._embellish_internal_event(self.action_args["event"]),
)
await self.send_default_status()
39 changes: 39 additions & 0 deletions ansible_rulebook/action/print_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys
from pprint import pprint
from typing import Callable

from .base_action import BaseAction
from .control import Control
from .metadata import Metadata


class PrintEvent(BaseAction):
def __init__(self, metadata: Metadata, control: Control, **action_args):
super().__init__(metadata, control, "print_event")

self.action_args = action_args

async def __call__(self):
print_fn: Callable = print
if "pretty" in self.action_args:
print_fn = pprint

var_name = "events" if "events" in self.control.variables else "event"

print_fn(self.control.variables[var_name])
sys.stdout.flush()
await self.send_default_status()
44 changes: 44 additions & 0 deletions ansible_rulebook/action/retract_fact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging

from drools import ruleset as lang

from .base_action import BaseAction
from .control import Control
from .metadata import Metadata

logger = logging.getLogger(__name__)


class RetractFact(BaseAction):
def __init__(self, metadata: Metadata, control: Control, **action_args):
super().__init__(metadata, control, "retract_fact")
self.action_args = action_args

async def __call__(self):
partial = self.action_args.get("partial", True)
if not partial:
exclude_keys = ["meta"]
else:
exclude_keys = []

lang.retract_matching_facts(
self.action_args["ruleset"],
self.action_args["fact"],
partial,
exclude_keys,
)
await self.send_default_status()
Loading

0 comments on commit 51d3968

Please sign in to comment.