Skip to content

Commit 1931b5f

Browse files
committed
Convert BaseConfig/RunnerConfig to dataclass
1 parent ca0df81 commit 1931b5f

File tree

6 files changed

+162
-154
lines changed

6 files changed

+162
-154
lines changed

src/ansible_runner/config/_base.py

+68-75
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import tempfile
3030
import shutil
3131
from base64 import b64encode
32+
from dataclasses import dataclass, field
3233
from enum import Enum
3334
from uuid import uuid4
3435
from collections.abc import Mapping
@@ -61,96 +62,81 @@ class BaseExecutionMode(Enum):
6162
GENERIC_COMMANDS = 2
6263

6364

65+
@dataclass
66+
class _ArgField(dict):
67+
required: bool = True
68+
69+
def __getattr__(self, attr):
70+
return self[attr]
71+
72+
73+
@dataclass
6474
class BaseConfig:
6575

66-
def __init__(self,
67-
private_data_dir: str | None = None,
68-
host_cwd: str | None = None,
69-
envvars: dict[str, Any] | None = None,
70-
passwords=None,
71-
settings=None,
72-
project_dir: str | None = None,
73-
artifact_dir: str | None = None,
74-
fact_cache_type: str = 'jsonfile',
75-
fact_cache=None,
76-
process_isolation: bool = False,
77-
process_isolation_executable: str | None = None,
78-
container_image: str = "",
79-
container_volume_mounts=None,
80-
container_options=None,
81-
container_workdir: str | None = None,
82-
container_auth_data=None,
83-
ident: str | None = None,
84-
rotate_artifacts: int = 0,
85-
timeout: int | None = None,
86-
ssh_key: str | None = None,
87-
quiet: bool = False,
88-
json_mode: bool = False,
89-
check_job_event_data: bool = False,
90-
suppress_env_files: bool = False,
91-
keepalive_seconds: int | None = None
92-
):
76+
private_data_dir: str | None = field(metadata=_ArgField(), default=None)
77+
host_cwd: str | None = field(metadata=_ArgField(), default=None)
78+
envvars: dict[str, Any] | None = field(metadata=_ArgField(), default=None)
79+
passwords: dict[str, str] | None = field(metadata=_ArgField(), default=None)
80+
settings: dict | None = field(metadata=_ArgField(), default=None)
81+
project_dir: str | None = field(metadata=_ArgField(), default=None)
82+
artifact_dir: str | None = field(metadata=_ArgField(), default=None)
83+
fact_cache_type: str = field(metadata=_ArgField(), default='jsonfile')
84+
fact_cache: str | None = field(metadata=_ArgField(), default=None)
85+
process_isolation: bool = field(metadata=_ArgField(), default=False)
86+
process_isolation_executable: str = field(metadata=_ArgField(), default=defaults.default_process_isolation_executable)
87+
container_image: str = field(metadata=_ArgField(), default="")
88+
container_volume_mounts: list[str] | None = field(metadata=_ArgField(), default=None)
89+
container_options: list[str] | None = field(metadata=_ArgField(), default=None)
90+
container_workdir: str | None = field(metadata=_ArgField(), default=None)
91+
container_auth_data: dict[str, str] | None = field(metadata=_ArgField(), default=None)
92+
ident: str | None = field(metadata=_ArgField(), default=None)
93+
rotate_artifacts: int = field(metadata=_ArgField(), default=0)
94+
timeout: int | None = field(metadata=_ArgField(), default=None)
95+
ssh_key: str | None = field(metadata=_ArgField(), default=None)
96+
quiet: bool = field(metadata=_ArgField(), default=False)
97+
json_mode: bool = field(metadata=_ArgField(), default=False)
98+
check_job_event_data: bool = field(metadata=_ArgField(), default=False)
99+
suppress_env_files: bool = field(metadata=_ArgField(), default=False)
100+
keepalive_seconds: int | None = field(metadata=_ArgField(), default=None)
101+
102+
_CONTAINER_ENGINES = ('docker', 'podman')
103+
104+
def __post_init__(self) -> None:
93105
# pylint: disable=W0613
94106

95-
# common params
96-
self.host_cwd = host_cwd
97-
self.envvars = envvars
98-
self.ssh_key_data = ssh_key
99107
self.command: list[str] = []
100-
101-
# container params
102-
self.process_isolation = process_isolation
103-
self.process_isolation_executable = process_isolation_executable or defaults.default_process_isolation_executable
104-
self.container_image = container_image
105-
self.container_volume_mounts = container_volume_mounts
106-
self.container_workdir = container_workdir
107-
self.container_auth_data = container_auth_data
108108
self.registry_auth_path: str
109109
self.container_name: str = "" # like other properties, not accurate until prepare is called
110-
self.container_options = container_options
111-
112-
# runner params
113-
self.rotate_artifacts = rotate_artifacts
114-
self.quiet = quiet
115-
self.json_mode = json_mode
116-
self.passwords = passwords
117-
self.settings = settings
118-
self.timeout = timeout
119-
self.check_job_event_data = check_job_event_data
120-
self.suppress_env_files = suppress_env_files
110+
121111
# ignore this for now since it's worker-specific and would just trip up old runners
122112
# self.keepalive_seconds = keepalive_seconds
123113

124114
# setup initial environment
125-
if private_data_dir:
126-
self.private_data_dir = os.path.abspath(private_data_dir)
115+
if self.private_data_dir:
116+
self.private_data_dir = os.path.abspath(self.private_data_dir)
127117
# Note that os.makedirs, exist_ok=True is dangerous. If there's a directory writable
128118
# by someone other than the user anywhere in the path to be created, an attacker can
129119
# attempt to compromise the directories via a race.
130120
os.makedirs(self.private_data_dir, exist_ok=True, mode=0o700)
131121
else:
132122
self.private_data_dir = tempfile.mkdtemp(prefix=defaults.AUTO_CREATE_NAMING, dir=defaults.AUTO_CREATE_DIR)
133123

134-
if artifact_dir is None:
135-
artifact_dir = os.path.join(self.private_data_dir, 'artifacts')
124+
if self.artifact_dir is None:
125+
self.artifact_dir = os.path.join(self.private_data_dir, 'artifacts')
136126
else:
137-
artifact_dir = os.path.abspath(artifact_dir)
127+
self.artifact_dir = os.path.abspath(self.artifact_dir)
138128

139-
if ident is None:
129+
if self.ident is None:
140130
self.ident = str(uuid4())
141131
else:
142-
self.ident = str(ident)
132+
self.ident = str(self.ident)
143133

144-
self.artifact_dir = os.path.join(artifact_dir, self.ident)
134+
self.artifact_dir = os.path.join(self.artifact_dir, self.ident)
145135

146-
if not project_dir:
136+
if not self.project_dir:
147137
self.project_dir = os.path.join(self.private_data_dir, 'project')
148-
else:
149-
self.project_dir = project_dir
150138

151-
self.rotate_artifacts = rotate_artifacts
152-
self.fact_cache_type = fact_cache_type
153-
self.fact_cache = os.path.join(self.artifact_dir, fact_cache or 'fact_cache') if self.fact_cache_type == 'jsonfile' else None
139+
self.fact_cache = os.path.join(self.artifact_dir, self.fact_cache or 'fact_cache') if self.fact_cache_type == 'jsonfile' else None
154140

155141
self.loader = ArtifactLoader(self.private_data_dir)
156142

@@ -162,12 +148,19 @@ def __init__(self,
162148

163149
os.makedirs(self.artifact_dir, exist_ok=True, mode=0o700)
164150

165-
_CONTAINER_ENGINES = ('docker', 'podman')
166-
167151
@property
168152
def containerized(self):
169153
return self.process_isolation and self.process_isolation_executable in self._CONTAINER_ENGINES
170154

155+
@property
156+
def ssh_key_data(self):
157+
""" Alias for backward compatibility. """
158+
return self.ssh_key
159+
160+
@ssh_key_data.setter
161+
def ssh_key_data(self, value):
162+
self.ssh_key = value
163+
171164
def prepare_env(self, runner_mode: str = 'pexpect') -> None:
172165
"""
173166
Manages reading environment metadata files under ``private_data_dir`` and merging/updating
@@ -178,7 +171,7 @@ def prepare_env(self, runner_mode: str = 'pexpect') -> None:
178171
if self.settings and isinstance(self.settings, dict):
179172
self.settings.update(self.loader.load_file('env/settings', Mapping)) # type: ignore
180173
else:
181-
self.settings = self.loader.load_file('env/settings', Mapping)
174+
self.settings = self.loader.load_file('env/settings', Mapping) # type: ignore
182175
except ConfigurationError:
183176
debug("Not loading settings")
184177
self.settings = {}
@@ -188,11 +181,11 @@ def prepare_env(self, runner_mode: str = 'pexpect') -> None:
188181
if self.passwords and isinstance(self.passwords, dict):
189182
self.passwords.update(self.loader.load_file('env/passwords', Mapping)) # type: ignore
190183
else:
191-
self.passwords = self.passwords or self.loader.load_file('env/passwords', Mapping)
184+
self.passwords = self.passwords or self.loader.load_file('env/passwords', Mapping) # type: ignore
192185
except ConfigurationError:
193186
debug('Not loading passwords')
194187

195-
self.expect_passwords = {}
188+
self.expect_passwords: dict[Any, Any] = {}
196189
try:
197190
if self.passwords:
198191
self.expect_passwords = {
@@ -268,16 +261,16 @@ def prepare_env(self, runner_mode: str = 'pexpect') -> None:
268261
# Still need to pass default environment to pexpect
269262

270263
try:
271-
if self.ssh_key_data is None:
272-
self.ssh_key_data = self.loader.load_file('env/ssh_key', str) # type: ignore
264+
if self.ssh_key is None:
265+
self.ssh_key = self.loader.load_file('env/ssh_key', str) # type: ignore
273266
except ConfigurationError:
274267
debug("Not loading ssh key")
275-
self.ssh_key_data = None
268+
self.ssh_key = None
276269

277270
# write the SSH key data into a fifo read by ssh-agent
278-
if self.ssh_key_data:
271+
if self.ssh_key:
279272
self.ssh_key_path = os.path.join(self.artifact_dir, 'ssh_key_data')
280-
open_fifo_write(self.ssh_key_path, self.ssh_key_data)
273+
open_fifo_write(self.ssh_key_path, self.ssh_key)
281274

282275
self.suppress_output_file = self.settings.get('suppress_output_file', False)
283276
self.suppress_ansible_output = self.settings.get('suppress_ansible_output', self.quiet)
@@ -340,7 +333,7 @@ def prepare_env(self, runner_mode: str = 'pexpect') -> None:
340333
debug(f' {k}: {v}')
341334

342335
def handle_command_wrap(self, execution_mode: BaseExecutionMode, cmdline_args: list[str]) -> None:
343-
if self.ssh_key_data:
336+
if self.ssh_key:
344337
logger.debug('ssh key data added')
345338
self.command = self.wrap_args_with_ssh_agent(self.command, self.ssh_key_path)
346339

0 commit comments

Comments
 (0)