Skip to content

Commit

Permalink
Add functionality to retrieve streamable arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
Shrews committed Oct 15, 2024
1 parent 5fbae20 commit 1121854
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 57 deletions.
69 changes: 37 additions & 32 deletions src/ansible_runner/config/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ class BaseExecutionMode(Enum):
GENERIC_COMMANDS = 2


# Metadata string values
class MetaValues(Enum):
STREAMABLE = 'streamable'


@dataclass
class BaseConfig:
"""The base configuration object.
Expand All @@ -77,38 +82,38 @@ class BaseConfig:
# No other config objects make use of positional parameters, so this should be fine.
#
# Example use case: RunnerConfig("/tmp/demo", playbook="main.yml", ...)
private_data_dir: str | None = field(metadata={}, default=None)

artifact_dir: str | None = field(metadata={}, default=None)
check_job_event_data: bool = field(metadata={}, default=False)
container_auth_data: dict[str, str] | None = field(metadata={}, default=None)
container_image: str = field(metadata={}, default="")
container_options: list[str] | None = field(metadata={}, default=None)
container_volume_mounts: list[str] | None = field(metadata={}, default=None)
container_workdir: str | None = field(metadata={}, default=None)
envvars: dict[str, Any] | None = field(metadata={}, default=None)
fact_cache: str | None = field(metadata={}, default=None)
fact_cache_type: str = field(metadata={}, default='jsonfile')
host_cwd: str | None = field(metadata={}, default=None)
ident: str | None = field(metadata={}, default=None)
json_mode: bool = field(metadata={}, default=False)
keepalive_seconds: int | None = field(metadata={}, default=None)
passwords: dict[str, str] | None = field(metadata={}, default=None)
process_isolation: bool = field(metadata={}, default=False)
process_isolation_executable: str = field(metadata={}, default=defaults.default_process_isolation_executable)
project_dir: str | None = field(metadata={}, default=None)
quiet: bool = field(metadata={}, default=False)
rotate_artifacts: int = field(metadata={}, default=0)
settings: dict | None = field(metadata={}, default=None)
ssh_key: str | None = field(metadata={}, default=None)
suppress_env_files: bool = field(metadata={}, default=False)
timeout: int | None = field(metadata={}, default=None)

event_handler: Callable[[dict], None] | None = None
status_handler: Callable[[dict, BaseConfig], bool] | None = None
artifacts_handler: Callable[[str], None] | None = None
cancel_callback: Callable[[], bool] | None = None
finished_callback: Callable[[BaseConfig], None] | None = None
private_data_dir: str | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)

artifact_dir: str | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
check_job_event_data: bool = False
container_auth_data: dict[str, str] | None = None
container_image: str = ""
container_options: list[str] | None = None
container_volume_mounts: list[str] | None = None
container_workdir: str | None = None
envvars: dict[str, Any] | None = None
fact_cache: str | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
fact_cache_type: str = 'jsonfile'
host_cwd: str | None = None
ident: str | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
json_mode: bool = False
keepalive_seconds: int | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
passwords: dict[str, str] | None = None
process_isolation: bool = False
process_isolation_executable: str = defaults.default_process_isolation_executable
project_dir: str | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
quiet: bool = False
rotate_artifacts: int = 0
settings: dict | None = None
ssh_key: str | None = None
suppress_env_files: bool = False
timeout: int | None = None

event_handler: Callable[[dict], None] | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
status_handler: Callable[[dict, BaseConfig], bool] | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
artifacts_handler: Callable[[str], None] | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
cancel_callback: Callable[[], bool] | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
finished_callback: Callable[[BaseConfig], None] | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)

_CONTAINER_ENGINES = ('docker', 'podman')

Expand Down
68 changes: 43 additions & 25 deletions src/ansible_runner/config/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@
import tempfile
import shutil

from dataclasses import dataclass, field
from dataclasses import dataclass, fields
from typing import Any

from ansible_runner import output
from ansible_runner.config._base import BaseConfig, BaseExecutionMode
from ansible_runner.config._base import BaseConfig, BaseExecutionMode, MetaValues
from ansible_runner.exceptions import ConfigurationError
from ansible_runner.output import debug
from ansible_runner.utils import register_for_cleanup
Expand Down Expand Up @@ -67,32 +68,32 @@ class RunnerConfig(BaseConfig):
"""

# 'binary' comes from the --binary CLI opt for an alternative ansible command path
binary: str | None = field(metadata={}, default=None)
cmdline: str | None = field(metadata={}, default=None)
directory_isolation_base_path: str | None = field(metadata={}, default=None)
extravars: dict | None = field(metadata={}, default=None)
forks: int | None = field(metadata={}, default=None)
host_pattern: str | None = field(metadata={}, default=None)
inventory: str | dict | list | None = field(metadata={}, default=None)
limit: str | None = field(metadata={}, default=None)
module: str | None = field(metadata={}, default=None)
module_args: str | None = field(metadata={}, default=None)
omit_event_data: bool = field(metadata={}, default=False)
only_failed_event_data: bool = field(metadata={}, default=False)
playbook: str | dict | list | None = field(metadata={}, default=None)
process_isolation_hide_paths: str | list | None = field(metadata={}, default=None)
process_isolation_ro_paths: str | list | None = field(metadata={}, default=None)
process_isolation_show_paths: str | list | None = field(metadata={}, default=None)
process_isolation_path: str | None = field(metadata={}, default=None)
binary: str | None = None
cmdline: str | None = None
directory_isolation_base_path: str | None = None
extravars: dict | None = None
forks: int | None = None
host_pattern: str | None = None
inventory: str | dict | list | None = None
limit: str | None = None
module: str | None = None
module_args: str | None = None
omit_event_data: bool = False
only_failed_event_data: bool = False
playbook: str | dict | list | None = None
process_isolation_hide_paths: str | list | None = None
process_isolation_ro_paths: str | list | None = None
process_isolation_show_paths: str | list | None = None
process_isolation_path: str | None = None
role: str = ""
role_skip_facts: bool = False
roles_path: str | None = field(metadata={}, default=None)
roles_path: str | None = None
role_vars: dict[str, str] | None = None
skip_tags: str | None = field(metadata={}, default=None)
suppress_ansible_output: bool = field(metadata={}, default=False)
suppress_output_file: bool = field(metadata={}, default=False)
tags: str | None = field(metadata={}, default=None)
verbosity: int | None = field(metadata={}, default=None)
skip_tags: str | None = None
suppress_ansible_output: bool = False
suppress_output_file: bool = False
tags: str | None = None
verbosity: int | None = None

def __post_init__(self) -> None:
# NOTE: Cannot call base class __init__() here as that causes some recursion madness.
Expand Down Expand Up @@ -148,6 +149,23 @@ def extra_vars(self):
def extra_vars(self, value):
self.extravars = value

def streamable_attributes(self) -> dict[str, Any]:
"""Get the set of streamable attributes that have a value that is different from the default.
The field metadata indicates if the attribute is streamable. By default, an attribute
is considered streamable (must be explicitly disabled).
:return: A dict of attribute names and their values.
"""
retval = {}
for field_obj in fields(self):
if field_obj.metadata and not field_obj.metadata.get(MetaValues.STREAMABLE, True):
continue
current_value = getattr(self, field_obj.name)
if not field_obj.default == current_value:
retval[field_obj.name] = current_value
return retval

def prepare(self):
"""
Performs basic checks and then properly invokes
Expand Down
22 changes: 22 additions & 0 deletions test/unit/config/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,3 +735,25 @@ def test_containerization_settings(tmp_path, runtime, mocker):
['my_container', 'ansible-playbook', '-i', '/runner/inventory', 'main.yaml']

assert expected_command_start == rc.command


def test_streamable_attributes_all_defaults():
"""Test that all default values return an empty dict."""
rc = RunnerConfig()
assert not rc.streamable_attributes()


def test_streamable_attributes_non_default(tmp_path):
"""Test that non-default, streamable values are returned."""
rc = RunnerConfig(private_data_dir=str(tmp_path),
keepalive_seconds=10,
host_pattern="hostA,",
json_mode=True,
verbosity=3)

# Don't expect private_data_dir or keepalive_seconds since they are not streamable.
assert rc.streamable_attributes() == {
"host_pattern": "hostA,",
"json_mode": True,
"verbosity": 3,
}

0 comments on commit 1121854

Please sign in to comment.