Skip to content

Commit 9be5cd1

Browse files
committed
Have streaming objects get values from RunnerConfig
1 parent 1121854 commit 9be5cd1

File tree

7 files changed

+100
-83
lines changed

7 files changed

+100
-83
lines changed

src/ansible_runner/config/_base.py

+21-14
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class BaseExecutionMode(Enum):
6464

6565
# Metadata string values
6666
class MetaValues(Enum):
67-
STREAMABLE = 'streamable'
67+
TRANSMIT = 'transmit'
6868

6969

7070
@dataclass
@@ -82,38 +82,38 @@ class BaseConfig:
8282
# No other config objects make use of positional parameters, so this should be fine.
8383
#
8484
# Example use case: RunnerConfig("/tmp/demo", playbook="main.yml", ...)
85-
private_data_dir: str | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
85+
private_data_dir: str | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
8686

87-
artifact_dir: str | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
87+
artifact_dir: str | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
8888
check_job_event_data: bool = False
8989
container_auth_data: dict[str, str] | None = None
90-
container_image: str = ""
90+
container_image: str | None = None
9191
container_options: list[str] | None = None
9292
container_volume_mounts: list[str] | None = None
9393
container_workdir: str | None = None
9494
envvars: dict[str, Any] | None = None
95-
fact_cache: str | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
95+
fact_cache: str | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
9696
fact_cache_type: str = 'jsonfile'
9797
host_cwd: str | None = None
98-
ident: str | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
98+
ident: str | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
9999
json_mode: bool = False
100-
keepalive_seconds: int | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
100+
keepalive_seconds: int | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
101101
passwords: dict[str, str] | None = None
102102
process_isolation: bool = False
103103
process_isolation_executable: str = defaults.default_process_isolation_executable
104-
project_dir: str | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
104+
project_dir: str | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
105105
quiet: bool = False
106106
rotate_artifacts: int = 0
107107
settings: dict | None = None
108108
ssh_key: str | None = None
109109
suppress_env_files: bool = False
110110
timeout: int | None = None
111111

112-
event_handler: Callable[[dict], None] | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
113-
status_handler: Callable[[dict, BaseConfig], bool] | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
114-
artifacts_handler: Callable[[str], None] | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
115-
cancel_callback: Callable[[], bool] | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
116-
finished_callback: Callable[[BaseConfig], None] | None = field(metadata={MetaValues.STREAMABLE: False}, default=None)
112+
event_handler: Callable[[dict], None] | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
113+
status_handler: Callable[[dict, BaseConfig], bool] | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
114+
artifacts_handler: Callable[[str], None] | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
115+
cancel_callback: Callable[[], bool] | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
116+
finished_callback: Callable[[BaseConfig], None] | None = field(metadata={MetaValues.TRANSMIT: False}, default=None)
117117

118118
_CONTAINER_ENGINES = ('docker', 'podman')
119119

@@ -123,6 +123,8 @@ def __post_init__(self) -> None:
123123
self.command: list[str] = []
124124
self.registry_auth_path: str
125125
self.container_name: str = "" # like other properties, not accurate until prepare is called
126+
if self.container_image is None:
127+
self.container_image = ''
126128

127129
# ignore this for now since it's worker-specific and would just trip up old runners
128130
# self.keepalive_seconds = keepalive_seconds
@@ -139,6 +141,7 @@ def __post_init__(self) -> None:
139141
raise ConfigurationError(f"Unable to create private_data_dir {self.private_data_dir}") from error
140142
else:
141143
self.private_data_dir = tempfile.mkdtemp(prefix=defaults.AUTO_CREATE_NAMING, dir=defaults.AUTO_CREATE_DIR)
144+
register_for_cleanup(self.private_data_dir)
142145

143146
if self.artifact_dir is None:
144147
self.artifact_dir = os.path.join(self.private_data_dir, 'artifacts')
@@ -147,7 +150,9 @@ def __post_init__(self) -> None:
147150

148151
if self.ident is None:
149152
self.ident = str(uuid4())
153+
self.ident_set_by_user = False
150154
else:
155+
self.ident_set_by_user = True
151156
self.ident = str(self.ident)
152157

153158
self.artifact_dir = os.path.join(self.artifact_dir, self.ident)
@@ -185,7 +190,6 @@ def prepare_env(self, runner_mode: str = 'pexpect') -> None:
185190
Manages reading environment metadata files under ``private_data_dir`` and merging/updating
186191
with existing values so the :py:class:`ansible_runner.runner.Runner` object can read and use them easily
187192
"""
188-
189193
if self.ident is None:
190194
raise ConfigurationError("ident value cannot be None")
191195
if self.artifact_dir is None:
@@ -520,6 +524,9 @@ def wrap_args_for_containerization(self,
520524
if self.private_data_dir is None:
521525
raise ConfigurationError("private_data_dir value cannot be None")
522526

527+
if self.container_image is None:
528+
raise ConfigurationError("container_image value cannot be None")
529+
523530
new_args = [self.process_isolation_executable]
524531
new_args.extend(['run', '--rm'])
525532

src/ansible_runner/config/runner.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,27 @@ def extra_vars(self, value):
152152
def streamable_attributes(self) -> dict[str, Any]:
153153
"""Get the set of streamable attributes that have a value that is different from the default.
154154
155-
The field metadata indicates if the attribute is streamable. By default, an attribute
155+
The field metadata indicates if the attribute is streamable from Transmit. By default, an attribute
156156
is considered streamable (must be explicitly disabled).
157157
158158
:return: A dict of attribute names and their values.
159159
"""
160160
retval = {}
161161
for field_obj in fields(self):
162-
if field_obj.metadata and not field_obj.metadata.get(MetaValues.STREAMABLE, True):
162+
if field_obj.metadata and not field_obj.metadata.get(MetaValues.TRANSMIT, True):
163163
continue
164164
current_value = getattr(self, field_obj.name)
165-
if not field_obj.default == current_value:
166-
retval[field_obj.name] = current_value
165+
166+
if field_obj.default == current_value:
167+
continue
168+
169+
# Treat an empty current value (e.g., {} or "") as the same as a default of None to prevent
170+
# streaming unnecessary empty values.
171+
if field_obj.default is None and current_value in ({}, "", []):
172+
continue
173+
174+
retval[field_obj.name] = current_value
175+
167176
return retval
168177

169178
def prepare(self):

src/ansible_runner/interface.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import sys
2626
import threading
2727
import logging
28-
from dataclasses import asdict
2928

3029
from ansible_runner import output
3130
from ansible_runner._internal._dump_artifacts import dump_artifacts
@@ -90,18 +89,15 @@ def init_runner(
9089
config.cancel_callback = signal_handler()
9190

9291
if streamer == 'transmit':
93-
kwargs = asdict(config)
94-
stream_transmitter = Transmitter(only_transmit_kwargs, _output=_output, **kwargs)
92+
stream_transmitter = Transmitter(config, only_transmit_kwargs, _output=_output)
9593
return stream_transmitter
9694

9795
if streamer == 'worker':
98-
kwargs = asdict(config)
99-
stream_worker = Worker(_input=_input, _output=_output, **kwargs)
96+
stream_worker = Worker(config, _input=_input, _output=_output)
10097
return stream_worker
10198

10299
if streamer == 'process':
103-
kwargs = asdict(config)
104-
stream_processor = Processor(_input=_input, **kwargs)
100+
stream_processor = Processor(config, _input=_input)
105101
return stream_processor
106102

107103
if config.process_isolation:

src/ansible_runner/streaming.py

+23-36
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import os
66
import stat
77
import sys
8-
import tempfile
98
import uuid
109
import traceback
1110

@@ -15,10 +14,10 @@
1514
from typing import BinaryIO
1615

1716
import ansible_runner
17+
from ansible_runner.config.runner import RunnerConfig
1818
from ansible_runner.exceptions import ConfigurationError
1919
from ansible_runner.loader import ArtifactLoader
2020
import ansible_runner.plugins
21-
from ansible_runner.utils import register_for_cleanup
2221
from ansible_runner.utils.streaming import stream_dir, unstream_dir
2322

2423

@@ -38,16 +37,14 @@ def __init__(self, settings):
3837

3938

4039
class Transmitter:
41-
def __init__(self, only_transmit_kwargs: bool, _output: BinaryIO | None, **kwargs):
40+
def __init__(self, config: RunnerConfig, only_transmit_kwargs: bool = False, _output: BinaryIO | None = None):
4241
if _output is None:
4342
_output = sys.stdout.buffer
4443
self._output = _output
45-
self.private_data_dir = os.path.abspath(kwargs['private_data_dir'])
44+
self.private_data_dir = os.path.abspath(config.private_data_dir) if config.private_data_dir else ""
4645
self.only_transmit_kwargs = only_transmit_kwargs
47-
if 'keepalive_seconds' in kwargs:
48-
kwargs.pop('keepalive_seconds') # don't confuse older runners with this Worker-only arg
4946

50-
self.kwargs = kwargs
47+
self.kwargs = config.streamable_attributes()
5148

5249
self.status = "unstarted"
5350
self.rc = None
@@ -70,12 +67,13 @@ def run(self):
7067

7168

7269
class Worker:
73-
def __init__(self, _input=None, _output=None, keepalive_seconds: float | None = None, **kwargs):
70+
def __init__(self, config: RunnerConfig, _input=None, _output=None):
7471
if _input is None:
7572
_input = sys.stdin.buffer
7673
if _output is None:
7774
_output = sys.stdout.buffer
7875

76+
keepalive_seconds: float | int | None = config.keepalive_seconds
7977
if keepalive_seconds is None: # if we didn't get an explicit int value, fall back to envvar
8078
# FIXME: emit/log a warning and silently continue if this value won't parse
8179
keepalive_seconds = float(os.environ.get('ANSIBLE_RUNNER_KEEPALIVE_SECONDS', 0))
@@ -88,14 +86,10 @@ def __init__(self, _input=None, _output=None, keepalive_seconds: float | None =
8886
self._input = _input
8987
self._output = _output
9088

91-
self.kwargs = kwargs
89+
self.kwargs = config.streamable_attributes()
9290
self.job_kwargs = None
9391

94-
private_data_dir = kwargs.get('private_data_dir')
95-
if private_data_dir is None:
96-
private_data_dir = tempfile.mkdtemp()
97-
register_for_cleanup(private_data_dir)
98-
self.private_data_dir = private_data_dir
92+
self.private_data_dir = config.private_data_dir
9993

10094
self.status = "unstarted"
10195
self.rc = None
@@ -251,43 +245,36 @@ def finished_callback(self, runner_obj):
251245

252246

253247
class Processor:
254-
def __init__(self, _input=None, status_handler=None, event_handler=None,
255-
artifacts_handler=None, cancel_callback=None, finished_callback=None, **kwargs):
248+
def __init__(self, config: RunnerConfig, _input: BinaryIO | None = None):
256249
if _input is None:
257250
_input = sys.stdin.buffer
258251
self._input = _input
259252

260-
self.quiet = kwargs.get('quiet')
253+
self.quiet = config.quiet
261254

262-
private_data_dir = kwargs.get('private_data_dir')
263-
if private_data_dir is None:
264-
private_data_dir = tempfile.mkdtemp()
265-
self.private_data_dir = private_data_dir
255+
self.private_data_dir: str = config.private_data_dir or ''
266256
self._loader = ArtifactLoader(self.private_data_dir)
267257

268-
settings = kwargs.get('settings')
258+
settings = config.settings
269259
if settings is None:
270260
try:
271-
settings = self._loader.load_file('env/settings', Mapping)
261+
settings = self._loader.load_file('env/settings', Mapping) # type: ignore
272262
except ConfigurationError:
273263
settings = {}
274264
self.config = MockConfig(settings)
275265

276-
if kwargs.get('artifact_dir'):
277-
self.artifact_dir = os.path.abspath(kwargs.get('artifact_dir'))
278-
else:
279-
project_artifacts = os.path.abspath(os.path.join(self.private_data_dir, 'artifacts'))
280-
if ident := kwargs.get('ident'):
281-
self.artifact_dir = os.path.join(project_artifacts, str(ident))
282-
else:
283-
self.artifact_dir = project_artifacts
266+
self.artifact_dir = config.artifact_dir
267+
if self.artifact_dir and not config.ident_set_by_user:
268+
# If an ident value was not explicitly supplied, for some reason, we don't bother with
269+
# using a subdir named with the ident value.
270+
self.artifact_dir, _ = os.path.split(self.artifact_dir)
284271

285-
self.status_handler = status_handler
286-
self.event_handler = event_handler
287-
self.artifacts_handler = artifacts_handler
272+
self.status_handler = config.status_handler
273+
self.event_handler = config.event_handler
274+
self.artifacts_handler = config.artifacts_handler
288275

289-
self.cancel_callback = cancel_callback # FIXME: unused
290-
self.finished_callback = finished_callback
276+
self.cancel_callback = config.cancel_callback # FIXME: unused
277+
self.finished_callback = config.finished_callback
291278

292279
self.status = "unstarted"
293280
self.rc = None

0 commit comments

Comments
 (0)