Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Update stable 1.1 with main #726

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.1.1
current_version = 1.1.2
commit = False
tag = False
search = {current_version}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ jobs:
- "3.9"
- "3.10"
- "3.11"
- "3.12"

steps:
- name: Checkout
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/scheduled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ jobs:
- "3.9"
- "3.10"
- "3.11"
- "3.12"

steps:
- name: Checkout
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
## [Unreleased]
### Changed
### Added
- Add generic error message for unknown source errors
### Fixed
- Allow user to optionally include matching events
- Allow for fetching env and file contents from EDA server


## [1.1.1] - 2024-09-19
Expand Down
2 changes: 1 addition & 1 deletion ansible_rulebook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import yaml

__version__ = "1.1.1"
__version__ = "1.1.2"


def construct_vault_encrypted_unicode(loader, node):
Expand Down
12 changes: 10 additions & 2 deletions ansible_rulebook/action/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,23 @@ def embellish_internal_event(self, event: Dict) -> Dict:
def set_action(self, action) -> None:
self.action = action

def collect_extra_vars(self, user_extra_vars: Dict) -> Dict:
def collect_extra_vars(
self, user_extra_vars: Dict, include_events: bool = True
) -> Dict:
"""When we send information to ansible-playbook or job template
on AWX, we need the rule and event specific information to
on AWX, we need the rule and optionally 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

if the caller doesn't want to include events data return the
user_extra_vars.
"""
if not include_events:
return user_extra_vars

extra_vars = user_extra_vars.copy() if user_extra_vars else {}

eda_vars = {
Expand Down
4 changes: 3 additions & 1 deletion ansible_rulebook/action/run_job_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ async def __call__(self):
)

self.job_args["extra_vars"] = self.helper.collect_extra_vars(
self.job_args.get("extra_vars", {})
self.job_args.get("extra_vars", {}),
self.action_args.get("include_events", True),
)

await self._job_start_event()
await self._run()

Expand Down
6 changes: 2 additions & 4 deletions ansible_rulebook/action/run_playbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,8 @@ async def _pre_process(self) -> None:
os.mkdir(inventory_dir)

if self.helper.control.inventory:
create_inventory(inventory_dir, self.helper.control.inventory)
self.inventory = os.path.join(
inventory_dir, os.path.basename(self.helper.control.inventory)
self.inventory = create_inventory(
inventory_dir, self.helper.control.inventory
)
os.mkdir(project_dir)

Expand Down Expand Up @@ -270,7 +269,6 @@ def _get_latest_artifact(self, component: str, content: bool = True):
return files[0]

async def _untar_project(self, output_dir, project_data_file):

cmd = [tar, "zxvf", project_data_file]
proc = await asyncio.create_subprocess_exec(
*cmd,
Expand Down
5 changes: 3 additions & 2 deletions ansible_rulebook/action/run_workflow_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ async def __call__(self):
self.helper.metadata.rule_set,
self.helper.metadata.rule,
)

self.job_args["extra_vars"] = self.helper.collect_extra_vars(
self.job_args.get("extra_vars", {})
self.job_args.get("extra_vars", {}),
self.action_args.get("include_events", True),
)

await self._job_start_event()
await self._run()

Expand Down
12 changes: 11 additions & 1 deletion ansible_rulebook/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
from ansible_rulebook.engine import run_rulesets, start_source
from ansible_rulebook.job_template_runner import job_template_runner
from ansible_rulebook.rule_types import RuleSet, RuleSetQueue
from ansible_rulebook.util import decryptable
from ansible_rulebook.util import (
decryptable,
decrypted_context,
substitute_variables,
)
from ansible_rulebook.validators import Validate
from ansible_rulebook.vault import has_vaulted_str
from ansible_rulebook.websocket import (
Expand Down Expand Up @@ -74,6 +78,12 @@ async def run(parsed_args: argparse.Namespace) -> None:
raise WebSocketExchangeException(
"Error communicating with web socket server"
)
context = decrypted_context(startup_args.variables)
startup_args.env_vars = substitute_variables(
startup_args.env_vars, context
)
for k, v in startup_args.env_vars.items():
os.environ[k] = str(v)
else:
startup_args = StartupArgs()
startup_args.variables = load_vars(parsed_args)
Expand Down
1 change: 1 addition & 0 deletions ansible_rulebook/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ class StartupArgs:
inventory: str = field(default="")
check_controller_connection: bool = field(default=False)
check_vault: bool = field(default=True)
env_vars: Dict = field(default_factory=dict)
17 changes: 15 additions & 2 deletions ansible_rulebook/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,22 @@ async def start_source(
)
logger.debug("Task cancelled " + shutdown_msg)
except BaseException as e:
logger.error("Source error %s", str(e))
error_msg = str(e)
# Get the name of the exception class
error_type = str(type(e)).split(".")[-1].replace("'>", "")
if not error_msg:
user_msg = (
f"Unknown error {error_type}: "
"source plugin failed with no error message."
)
else:
user_msg = (
f"{error_type}: Source plugin failed with error message: "
f"'{error_msg}'"
)
logger.error("Source error: %s", user_msg)
shutdown_msg = (
f"Shutting down source: {source.source_name} error : {e}"
f"Shutting down source: {source.source_name} error: {user_msg}"
)
logger.error(shutdown_msg)
raise
Expand Down
7 changes: 0 additions & 7 deletions ansible_rulebook/job_template_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,6 @@ async def run_workflow_job_template(
"Workflow template %s does not accept limit, removing it", name
)
job_params.pop("limit")
if not obj["ask_variables_on_launch"] and "extra_vars" in job_params:
logger.warning(
"Workflow template %s does not accept extra vars, "
"removing it",
name,
)
job_params.pop("extra_vars")
job = await self._launch(job_params, url)
return await self._monitor_job(job["url"])

Expand Down
8 changes: 8 additions & 0 deletions ansible_rulebook/schema/ruleset_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,10 @@
},
"delay": {
"type": "integer"
},
"include_events": {
"type": "boolean",
"default": true
}
},
"required": [
Expand Down Expand Up @@ -502,6 +506,10 @@
},
"delay": {
"type": "integer"
},
"include_events": {
"type": "boolean",
"default": true
}
},
"required": [
Expand Down
7 changes: 6 additions & 1 deletion ansible_rulebook/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,17 +270,22 @@ async def send_session_stats(event_log: asyncio.Queue, stats: Dict):
)


def create_inventory(runner_inventory_dir: str, inventory: str) -> None:
def create_inventory(runner_inventory_dir: str, inventory: str) -> str:
if os.path.isfile(inventory):
shutil.copy(os.path.abspath(inventory), runner_inventory_dir)
inventory_path = os.path.join(
runner_inventory_dir, os.path.basename(inventory)
)
elif os.path.exists(inventory):
shutil.copytree(
os.path.abspath(inventory),
runner_inventory_dir,
dirs_exist_ok=True,
)
inventory_path = runner_inventory_dir
else:
raise InventoryNotFound(f"Inventory {inventory} not found")
return inventory_path


def _builtin_filter_path(name: str) -> Tuple[bool, str]:
Expand Down
31 changes: 31 additions & 0 deletions ansible_rulebook/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ async def _handle_request_workload(

project_data_fh = None
response = StartupArgs()
non_fq_key = False
file_template_vars = {}
while True:
msg = await websocket.recv()
data = json.loads(msg)
Expand All @@ -199,6 +201,21 @@ async def _handle_request_workload(
if not data.get("data") and not data.get("more"):
os.close(project_data_fh)
logger.debug("wrote %s", response.project_data_file)
if data.get("type") == "FileContents":
template_key = data.get("template_key")
raw_data = base64.b64decode(data.get("data"))
keys = template_key.split(".")
if len(keys) == 1 and template_key == "template":
key = "filename"
non_fq_key = True
else:
key = keys[1]
filename = tempfile.NamedTemporaryFile().name
with open(filename, "wb") as f:
f.write(raw_data)
file_template_vars[key] = filename
os.chmod(filename, 0o400)
logger.debug(f"File Content eda.filename.{key} : {filename}")
if data.get("type") == "Rulebook":
raw_data = base64.b64decode(data.get("data"))
response.check_vault = has_vaulted_str(raw_data)
Expand All @@ -209,12 +226,26 @@ async def _handle_request_workload(
response.variables = yaml.safe_load(
base64.b64decode(data.get("data"))
)
if data.get("type") == "EnvVars":
response.env_vars = yaml.safe_load(
base64.b64decode(data.get("data"))
)
if data.get("type") == "ControllerInfo":
response.controller_url = data.get("url")
response.controller_token = data.get("token")
response.controller_ssl_verify = data.get("ssl_verify")
response.controller_username = data.get("username", "")
response.controller_password = data.get("password", "")

if non_fq_key and "filename" in file_template_vars:
response.variables["eda"] = {
"filename": file_template_vars["filename"]
}
else:
response.variables["eda"] = {"filename": file_template_vars}

for key, value in response.env_vars.items():
response.variables[key] = value
return response


Expand Down
6 changes: 6 additions & 0 deletions docs/actions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ Run a job template.
* - organization
- The name of the organization
- Yes
* - include_events
- Should we include the matching events in the payload sent to controller. Default is true
- No
* - set_facts
- The artifacts from the job template execution are inserted back into the rule set as facts
- No
Expand Down Expand Up @@ -203,6 +206,9 @@ Run a workflow template.
* - organization
- The name of the organization
- Yes
* - include_events
- Should we include the matching events in the payload sent to controller. Default is true. If your workflow template does not have Prompt on Launch for Extra Variables or a Survey spec, you will have to set this to false.
- No
* - set_facts
- The artifacts from the workflow template execution are inserted back into the rule set as facts
- No
Expand Down
3 changes: 3 additions & 0 deletions docs/sources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ immediately after the ``put`` method.
The rulebook can contain it's own logic to finish the process through the ``shutdown`` action.
If your plugin needs to perform some cleanup before the process is terminated, you must catch the ``asyncio.CancelledError`` exception.

.. note::
Please, pay attention when handling errors in your plugin and ensure to raise an exception with a meaningful message so that ansible-rulebook
can log it correctly. Ansible-rulebook will not log the exception itself or print stack traces; it will only log the message you provide.

Distributing plugins
^^^^^^^^^^^^^^^^^^^^
Expand Down
31 changes: 16 additions & 15 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = ansible_rulebook
version = 1.1.1
version = 1.1.2
description = Event driven automation for Ansible
url = https://github.com/ansible/ansible-rulebook
license = Apache-2.0
Expand All @@ -16,26 +16,27 @@ classifiers =
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12

[options]
zip_safe = False
include_package_data = True
packages = find:
python_requires = >=3.9
install_requires =
aiohttp
pyparsing >= 3.0
jsonschema
jinja2
dpath >= 2.1.4
janus
ansible-runner
websockets
aiohttp >=3,<3.11
pyparsing >= 3.0,<4
jsonschema >=4,<5
jinja2 >=3,<4
dpath >= 2.1.4,<3
janus >=1,<2
ansible-runner >=2,<3
websockets >=10,<14
drools_jpy == 0.3.9
watchdog
xxhash
pyyaml
psycopg[binary]
watchdog >=3,<7
xxhash >=3,<4
pyyaml >=6,<7
psycopg[binary] >=3,<4

[options.packages.find]
include =
Expand All @@ -61,6 +62,6 @@ extend-ignore =

[options.extras_require]
production =
psycopg[c]
psycopg[c] >=3,<4
development =
psycopg[binary]
psycopg[binary] >=3,<4
Loading
Loading