-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Each action is a separate class Allows for us to share code between actions
- Loading branch information
Showing
34 changed files
with
2,977 additions
and
43 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# 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: | ||
"""Control information when running an action | ||
Attributes: | ||
queue: asyncio.Queue | ||
This is the queue on which we would be sending action status | ||
periodically when the action is running | ||
inventory: str | ||
This is the inventory information from the command line | ||
It currently is the data that is read from a file, in the future | ||
it could be a directory or an inventory name from the controller | ||
hosts: list[str] | ||
The list of servers passed into ansible-playbook or controller | ||
variables: dict | ||
The variables passed in from the command line plus the matching event | ||
data with event or events key. | ||
project_data_file: str | ||
This is the directory where the collection data is sent from the | ||
AAP server over the websocket is untarred to. The collection could | ||
contain the playbook that is used in the run_playbook action. | ||
""" | ||
|
||
__slots__ = [ | ||
"queue", | ||
"inventory", | ||
"hosts", | ||
"variables", | ||
"project_data_file", | ||
] | ||
queue: asyncio.Queue | ||
inventory: str | ||
hosts: List[str] | ||
variables: dict | ||
project_data_file: str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# 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 .control import Control | ||
from .helper import Helper | ||
from .metadata import Metadata | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class Debug: | ||
"""The debug action tries to mimic the ansible debug task with optional | ||
msg: Prints a message | ||
var: Prints a variable | ||
default: print the metadata, control information and facts from the | ||
rule engine | ||
At the end we send back the action status | ||
""" | ||
|
||
def __init__(self, metadata: Metadata, control: Control, **action_args): | ||
self.helper = Helper(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.helper.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.helper.metadata) | ||
project_data_file = self.helper.control.project_data_file | ||
args.update( | ||
{ | ||
"inventory": self.helper.control.inventory, | ||
"hosts": self.helper.control.hosts, | ||
"variables": self.helper.control.variables, | ||
"project_data_file": project_data_file, | ||
} | ||
) | ||
pprint(args) | ||
print(get_horizontal_rule("=")) | ||
print("facts:") | ||
pprint(lang.get_facts(self.helper.metadata.rule_set)) | ||
print(get_horizontal_rule("=")) | ||
|
||
sys.stdout.flush() | ||
await self.helper.send_default_status() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# 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 Helper: | ||
""" | ||
Helper class stores the metadata, the control attributes and has | ||
methods to send data to the Queue. | ||
Attributes | ||
---------- | ||
metadata : Metadata | ||
a data class that stores rule specific data | ||
control : Control | ||
a control dataclass that stores the runtime information about | ||
the queue on which we send the status for the action, the inventory | ||
information, the hosts data and the variables that we would like | ||
to pass into the action | ||
uuid : str | ||
each action has a uuid that is generated to track it | ||
action : str | ||
the name of the action, set by the sub classe | ||
Methods | ||
------- | ||
send_status(data={}, obj_type:"action") | ||
Sends the action status information on the queue | ||
send_default_status() | ||
Sends the default action status, used mostly with internal | ||
actions like debug, print_event, set_fact, retract_fact, | ||
noop, post_event | ||
get_events() | ||
Fetches the matching events from the variables | ||
collect_extra_vars() | ||
Create extra_vars to be sent to playbook and job template which | ||
includes rule and matching events. | ||
embellish_internal_event() | ||
Add internal sources for facts and events posted from inside of | ||
a rulebook | ||
""" | ||
|
||
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_status(self, data: Dict, obj_type: str = "Action") -> None: | ||
"""Send Action status information on the queue""" | ||
payload = { | ||
"type": obj_type, | ||
"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, | ||
"activation_instance_id": settings.identifier, | ||
} | ||
payload.update(data) | ||
await self.control.queue.put(payload) | ||
|
||
async def send_default_status(self): | ||
"""Send default action status information on the queue""" | ||
await self.send_status( | ||
{ | ||
"run_at": run_at(), | ||
"status": INTERNAL_ACTION_STATUS, | ||
"matching_events": self.get_events(), | ||
} | ||
) | ||
|
||
def get_events(self) -> Dict: | ||
"""From the control variables, detect if its a single event | ||
match or a multi event match and return a dictionary with | ||
the event data with | ||
m key for single event stored in the event key | ||
m_0,m_1,.... for multiple matching events stored in | ||
the events key | ||
""" | ||
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: | ||
"""Insert metadata for every internally generated event""" | ||
return insert_meta( | ||
event, **{"source_name": self.action, "source_type": "internal"} | ||
) | ||
|
||
def set_action(self, action) -> None: | ||
self.action = action | ||
|
||
def collect_extra_vars(self, user_extra_vars: Dict) -> Dict: | ||
"""When we send information to ansible-playbook or job template | ||
on AWX, we need the rule and event specific information to | ||
be sent to this external process | ||
the caller passes in the user_extra_vars from the action args | ||
and then we append eda specific vars and return that as a | ||
the updated dictionary that is sent to the external process | ||
""" | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# 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: | ||
"""Metadata class stores the rule specific information | ||
which is used when reporting stats for the action | ||
Attributes | ||
---------- | ||
rule: str | ||
Rule name | ||
rule_uuid: str | ||
Rule uuid | ||
rule_set: str | ||
Rule set name | ||
rule_set_uuid: str | ||
Rule set uuid | ||
rule_run_at: str | ||
ISO 8601 date/time when the rule was triggered | ||
""" | ||
|
||
__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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# 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 .control import Control | ||
from .helper import Helper | ||
from .metadata import Metadata | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class Noop: | ||
"""The No Op action usually used for debugging, doesn't do anything and | ||
just sends the action status | ||
""" | ||
|
||
def __init__(self, metadata: Metadata, control: Control, **action_args): | ||
self.helper = Helper(metadata, control, "noop") | ||
self.action_args = action_args | ||
|
||
async def __call__(self): | ||
await self.helper.send_default_status() |
Oops, something went wrong.