Skip to content

Commit

Permalink
feat: support receiving file contents and env vars from EDA Server (#711
Browse files Browse the repository at this point in the history
)

AAP Credential Types support File contents via file injectors.

https://docs.ansible.com/automation-controller/latest/html/userguide/credential_types.html

We create temporary files with the data sent from Server The source
plugin can access the temporary file names to access the data.

Supports single file as well as multiple files.

To access the filenames in the source rulebook you can use the following
extra vars

- eda.filename or
- eda.filename.<<key_name1>>
- eda.filename.<<key_name2>>

e.g.
```
- name: Use payload file to check events
  hosts: all
  sources:
    - ansible.eda.generic:
        payload_file: "{{ eda.filename.test_payload_file }}"
```

The env variables are treated like extra_vars and added to the current
processes environment

https://issues.redhat.com/browse/AAP-25519
  • Loading branch information
mkanoor authored Nov 22, 2024
1 parent 89524b7 commit 44618f0
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
### Added
### 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
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)
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
1 change: 1 addition & 0 deletions tests/data/test_cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a bogus certfile
3 changes: 3 additions & 0 deletions tests/data/test_env.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
ENV1: abc
ENV2: xyz
1 change: 1 addition & 0 deletions tests/data/test_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a bogus keyfile
66 changes: 56 additions & 10 deletions tests/test_websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import hashlib
import json
import os
from typing import Dict, List
from typing import Dict, List, Optional
from unittest import mock
from unittest.mock import AsyncMock, patch

Expand Down Expand Up @@ -42,17 +42,17 @@ def load_file(
data_list: List,
whole_file=False,
block_size: int = 1024,
additional_attributes: Optional[dict] = None,
) -> None:
with open(filename, "rb") as f:
if whole_file:
data_list.append(
json.dumps(
{
"type": data_type,
"data": base64.b64encode(f.read()).decode("ascii"),
}
).encode("utf-8")
)
payload = {
"type": data_type,
"data": base64.b64encode(f.read()).decode("ascii"),
}
if additional_attributes:
payload.update(additional_attributes)
data_list.append(json.dumps(payload).encode("utf-8"))
else:
while filedata := f.read(block_size):
data_list.append(
Expand All @@ -70,8 +70,29 @@ def load_file(
HERE = os.path.dirname(os.path.abspath(__file__))


@pytest.mark.parametrize(
("file_contents", "checks", "single"),
[
(
[
("./data/test_cert.pem", "template.cert_file"),
("./data/test_key.pem", "template.key_file"),
],
[
("cert_file", "./data/test_cert.pem"),
("key_file", "./data/test_key.pem"),
],
False,
),
(
[("./data/test_cert.pem", "template")],
[("filename", "./data/test_cert.pem")],
True,
),
],
)
@pytest.mark.asyncio
async def test_request_workload():
async def test_request_workload(file_contents, checks, single):
prepare_settings()
os.chdir(HERE)
controller_url = "https://www.example.com"
Expand All @@ -94,6 +115,16 @@ async def test_request_workload():
load_file("./rules/rules.yml", "Rulebook", test_data, True)
load_file("./playbooks/inventory.yml", "Inventory", test_data, True)
load_file("./data/test_vars.yml", "ExtraVars", test_data, True)
load_file("./data/test_env.yml", "EnvVars", test_data, True)
for file_name, var_name in file_contents:
load_file(
file_name,
"FileContents",
test_data,
True,
1024,
{"template_key": var_name},
)
test_data.append(dict2json({"type": "EndOfResponse"}))

with patch("ansible_rulebook.websocket.websockets.connect") as mo:
Expand All @@ -108,6 +139,21 @@ async def test_request_workload():
assert response.controller_ssl_verify == controller_ssl_verify
assert response.rulesets[0].name == "Demo rules"
assert len(response.rulesets[0].rules) == 6
assert response.env_vars["ENV1"] == "abc"
assert response.env_vars["ENV2"] == "xyz"
assert response.variables["ENV1"] == "abc"
assert response.variables["ENV2"] == "xyz"
for key, filename in checks:
if single:
with open(response.variables["eda"][key]) as f:
data = f.read()
else:
with open(response.variables["eda"]["filename"][key]) as f:
data = f.read()

with open(filename) as f:
original_data = f.read()
assert data == original_data


@pytest.mark.asyncio
Expand Down

0 comments on commit 44618f0

Please sign in to comment.