Skip to content

Commit fbedc73

Browse files
committed
Separate intrinsic environment variables
tmt-owned variables are no longer included in the `HasEnvironment.environment` return values. Separating the user-owned and tmt-owned variables will give us precise control over their ordering. Patch is also dropping support of `Environment.update(foo: HasEnvironment)` shortcut - an actual `Environment` instances are required now. Once a class offers both user-provided and tmt-owned variables, `update()` would have hard time to pick which set to include in the target environment ("both" might also be an answer...). This should be the penultimate patch of the environment precedence series; the next and almost-final patch will provide unified helper to construct environments so they follow the same process. Related to #2609.
1 parent 2b59267 commit fbedc73

11 files changed

Lines changed: 129 additions & 92 deletions

File tree

tmt/base/plan.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
style,
5858
to_yaml,
5959
)
60-
from tmt.utils.environment import Environment, EnvVarValue, HasEnvironment
60+
from tmt.utils.environment import Environment, EnvVarValue, HasEnvironment, HasIntrinsicEnvironment
6161

6262
if TYPE_CHECKING:
6363
import tmt.cli
@@ -224,6 +224,7 @@ class Plan(
224224
HasUserAnchorPath,
225225
HasPlanWorkdir,
226226
HasEnvironment,
227+
HasIntrinsicEnvironment,
227228
Core,
228229
tmt.export.Exportable['Plan'],
229230
tmt.lint.Lintable['Plan'],
@@ -458,11 +459,7 @@ def draw_test_serial_number(self, test: Test) -> int:
458459
_environment_from_importing: Environment = field(default_factory=Environment, internal=True)
459460

460461
@property
461-
def _environment_from_intrinsics(self) -> Environment:
462-
"""
463-
Environment variables derived from the plan properties.
464-
"""
465-
462+
def intrinsic_environment(self) -> Environment:
466463
environment = Environment(
467464
{
468465
'TMT_VERSION': EnvVarValue(tmt.__version__),
@@ -549,7 +546,6 @@ def environment(self) -> Environment:
549546
**self._environment_from_importing,
550547
**self._environment_from_cli,
551548
**self.my_run.environment,
552-
**self._environment_from_intrinsics,
553549
}
554550
)
555551

@@ -558,7 +554,6 @@ def environment(self) -> Environment:
558554
**self._environment_from_fmf,
559555
**self._environment_from_importing,
560556
**self._environment_from_cli,
561-
**self._environment_from_intrinsics,
562557
}
563558
)
564559

tmt/guest/__init__.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import tmt.steps
4343
import tmt.steps.scripts
4444
import tmt.utils
45+
import tmt.utils.environment
4546
import tmt.utils.wait
4647
from tmt._compat.typing import Self
4748
from tmt.ansible import (
@@ -1768,6 +1769,7 @@ class Guest(
17681769
# TODO: `Guest` does "have" environment, but it's a genuine attribute,
17691770
# not a property, and this interface will not work.
17701771
# tmt.utils.HasEnvironment,
1772+
tmt.utils.environment.HasIntrinsicEnvironment,
17711773
tmt.utils.Common,
17721774
):
17731775
"""
@@ -1983,6 +1985,14 @@ def plan_environment(self) -> Environment:
19831985

19841986
return Environment()
19851987

1988+
@property
1989+
def intrinsic_environment(self) -> Environment:
1990+
environment = Environment()
1991+
1992+
environment['TMT_PLAN_ENVIRONMENT_FILE'] = EnvVarValue(self.plan_environment_path)
1993+
1994+
return environment
1995+
19861996
@classmethod
19871997
def options(cls, how: Optional[str] = None) -> list[tmt.options.ClickOptionDecoratorType]:
19881998
"""
@@ -2307,14 +2317,12 @@ def _prepare_command_environment(
23072317
environment.update(self.environment)
23082318

23092319
if isinstance(self.parent, tmt.steps.Step):
2310-
environment.update(self.parent.plan)
2311-
2312-
# TODO: this was owned by plan, but at wrong position, and it will
2313-
# be owned by plan again once the dust of environment untangling
2314-
# settles. Follow https://github.com/teemtee/tmt/issues/4241 for
2315-
# more.
2316-
if self.plan_environment_path:
2317-
environment['TMT_PLAN_ENVIRONMENT_FILE'] = EnvVarValue(self.plan_environment_path)
2320+
environment.update(self.parent.plan.environment)
2321+
2322+
environment.update(self.intrinsic_environment)
2323+
2324+
if isinstance(self.parent, tmt.steps.Step):
2325+
environment.update(self.parent.plan.intrinsic_environment)
23182326

23192327
else:
23202328
# Create a copy of given environment - this prevents any

tmt/steps/context/abort.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import tmt.utils
66
from tmt.container import container
77
from tmt.utils import Path
8-
from tmt.utils.environment import Environment, HasEnvironment
8+
from tmt.utils.environment import Environment, HasIntrinsicEnvironment
99

1010

1111
class AbortStep(tmt.utils.GeneralError):
@@ -15,7 +15,7 @@ class AbortStep(tmt.utils.GeneralError):
1515

1616

1717
@container
18-
class AbortContext(HasEnvironment):
18+
class AbortContext(HasIntrinsicEnvironment):
1919
"""
2020
Provides API for handling a phase-requested abort of a step.
2121
"""
@@ -43,5 +43,5 @@ def requested(self) -> bool:
4343
return self.request_path.exists()
4444

4545
@property
46-
def environment(self) -> Environment:
46+
def intrinsic_environment(self) -> Environment:
4747
return Environment()

tmt/steps/context/pidfile.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
from tmt.guest import Guest, TransferOptions
8585
from tmt.steps import safe_filename
8686
from tmt.utils import Path, ShellScript
87-
from tmt.utils.environment import Environment, EnvVarValue, HasEnvironment
87+
from tmt.utils.environment import Environment, EnvVarValue, HasIntrinsicEnvironment
8888
from tmt.utils.templates import render_template
8989

9090
TEST_PIDFILE_FILENAME = 'tmt-test.pid'
@@ -193,7 +193,7 @@ def effective_pidfile_root() -> Path:
193193

194194

195195
@container
196-
class PidFileContext(HasEnvironment):
196+
class PidFileContext(HasIntrinsicEnvironment):
197197
#: Phase owning this context.
198198
phase: tmt.steps.BasePlugin[Any, Any]
199199

@@ -220,7 +220,7 @@ def pidfile_lock_path(self) -> Path:
220220
return effective_pidfile_root() / TEST_PIDFILE_LOCK_FILENAME
221221

222222
@property
223-
def environment(self) -> Environment:
223+
def intrinsic_environment(self) -> Environment:
224224
return Environment(
225225
{
226226
'TMT_TEST_PIDFILE': EnvVarValue(self.pidfile_path),

tmt/steps/context/reboot.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from tmt.container import MetadataContainer, container
1010
from tmt.guest import Guest, RebootMode, SoftRebootModes
1111
from tmt.utils import Path, ShellScript
12-
from tmt.utils.environment import Environment, EnvVarValue, HasEnvironment
12+
from tmt.utils.environment import Environment, EnvVarValue, HasIntrinsicEnvironment
1313
from tmt.utils.wait import Deadline, Waiting
1414

1515
if TYPE_CHECKING:
@@ -27,7 +27,7 @@ class RebootData(MetadataContainer):
2727

2828

2929
@container
30-
class RebootContext(HasEnvironment):
30+
class RebootContext(HasIntrinsicEnvironment):
3131
"""
3232
Tracks information about guest reboots.
3333
"""
@@ -76,7 +76,7 @@ def requested(self) -> bool:
7676
return self.soft_requested or self.hard_requested
7777

7878
@property
79-
def environment(self) -> Environment:
79+
def intrinsic_environment(self) -> Environment:
8080
environment = Environment()
8181

8282
# Set all supported reboot variables

tmt/steps/context/restart.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import tmt.utils
55
from tmt.container import container
66
from tmt.guest import Guest
7-
from tmt.utils.environment import Environment, EnvVarValue, HasEnvironment
7+
from tmt.utils.environment import Environment, EnvVarValue, HasIntrinsicEnvironment
88

99
if TYPE_CHECKING:
1010
from tmt.steps.context.reboot import RebootContext
1111

1212

1313
@container
14-
class RestartContext(HasEnvironment):
14+
class RestartContext(HasIntrinsicEnvironment):
1515
"""
1616
Tracks information about restarts of an action, e.g. a test script.
1717
"""
@@ -50,7 +50,7 @@ def requested(self) -> bool:
5050
return self.is_requested_test()
5151

5252
@property
53-
def environment(self) -> Environment:
53+
def intrinsic_environment(self) -> Environment:
5454
return Environment({'TMT_TEST_RESTART_COUNT': EnvVarValue(str(self.restart_counter))})
5555

5656
def handle_restart(self, reboot: Optional['RebootContext'] = None) -> bool:

tmt/steps/context/restraint.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
import tmt.log
44
from tmt.container import container
5-
from tmt.utils.environment import Environment, EnvVarValue, HasEnvironment
5+
from tmt.utils.environment import Environment, EnvVarValue, HasIntrinsicEnvironment
66

77

88
@container
9-
class RestraintContext(HasEnvironment):
9+
class RestraintContext(HasIntrinsicEnvironment):
1010
"""
1111
Provides restraint-related context for execution.
1212
"""
@@ -22,7 +22,7 @@ class RestraintContext(HasEnvironment):
2222
taskname: Optional[str] = None
2323

2424
@property
25-
def environment(self) -> Environment:
25+
def intrinsic_environment(self) -> Environment:
2626
environment = Environment()
2727

2828
environment["TMT_RESTRAINT_COMPATIBLE"] = EnvVarValue(str(int(self.enabled)))

tmt/steps/execute/__init__.py

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
Stopwatch,
4949
configure_bool_constant,
5050
)
51-
from tmt.utils.environment import Environment, EnvVarValue, HasEnvironment
51+
from tmt.utils.environment import Environment, EnvVarValue, HasEnvironment, HasIntrinsicEnvironment
5252

5353
if TYPE_CHECKING:
5454
import tmt.base.plan
@@ -111,7 +111,7 @@ class ExecuteStepData(tmt.steps.WhereableStepData, tmt.steps.StepData):
111111

112112

113113
@container
114-
class TestInvocation(HasStepWorkdir, HasEnvironment):
114+
class TestInvocation(HasStepWorkdir, HasEnvironment, HasIntrinsicEnvironment):
115115
"""
116116
A bundle describing one test invocation.
117117
@@ -352,56 +352,62 @@ def restraint(self) -> RestraintContext:
352352
logger=self.logger,
353353
)
354354

355+
@property
356+
def intrinsic_environment(self) -> Environment:
357+
# narrow type
358+
assert isinstance(self.phase.step.plan.my_run, tmt.base.run.Run)
359+
360+
environment = Environment()
361+
362+
environment["TMT_TEST_NAME"] = EnvVarValue(self.test.name)
363+
environment["TMT_TEST_INVOCATION_PATH"] = EnvVarValue(self.path)
364+
environment["TMT_TEST_DATA"] = EnvVarValue(self.test_data_path)
365+
environment["TMT_TEST_SUBMITTED_FILES"] = EnvVarValue(self.submission_log_path)
366+
environment['TMT_TEST_SERIAL_NUMBER'] = EnvVarValue(str(self.test.serial_number))
367+
environment["TMT_TEST_METADATA"] = EnvVarValue(self.path / TEST_METADATA_FILENAME)
368+
369+
environment['TMT_TEST_ITERATION_ID'] = EnvVarValue(
370+
f"{self.phase.step.plan.my_run.unique_id}-{self.test.serial_number}"
371+
)
372+
373+
environment['TMT_SOURCE_DIR'] = EnvVarValue(self.discover_phase.source_dir)
374+
375+
environment.update(
376+
# Add variables from plan
377+
self.phase.step.plan.intrinsic_environment,
378+
# Add variables from guest
379+
self.guest.intrinsic_environment,
380+
# Add variables from invocation contexts
381+
self.abort.intrinsic_environment,
382+
self.reboot.intrinsic_environment,
383+
self.restart.intrinsic_environment,
384+
self.pidfile.intrinsic_environment,
385+
self.restraint.intrinsic_environment,
386+
# Add variables the framework wants to expose
387+
self.test.test_framework.get_environment_variables(self, self.logger),
388+
)
389+
390+
return environment
391+
355392
@property
356393
def environment(self) -> Environment:
357394
if self._environment is None:
358395
# narrow type
359-
assert isinstance(self.phase.parent, Execute)
360-
361-
# narrow type
362-
assert isinstance(self.phase.parent.plan.my_run, tmt.base.run.Run)
396+
assert isinstance(self.phase.step.plan.my_run, tmt.base.run.Run)
363397

364398
environment = Environment()
365399

366400
environment.update(
367401
self.guest.environment,
368402
self.test.environment,
369403
self.guest.plan_environment,
370-
self.phase.parent.plan.environment,
404+
self.phase.step.plan.environment,
371405
)
372406

373-
environment["TMT_TEST_NAME"] = EnvVarValue(self.test.name)
374-
environment["TMT_TEST_INVOCATION_PATH"] = EnvVarValue(self.path)
375-
environment["TMT_TEST_DATA"] = EnvVarValue(self.test_data_path)
376-
environment["TMT_TEST_SUBMITTED_FILES"] = EnvVarValue(self.submission_log_path)
377-
environment['TMT_TEST_SERIAL_NUMBER'] = EnvVarValue(str(self.test.serial_number))
378-
environment["TMT_TEST_METADATA"] = EnvVarValue(self.path / TEST_METADATA_FILENAME)
379-
380-
environment['TMT_TEST_ITERATION_ID'] = EnvVarValue(
381-
f"{self.phase.parent.plan.my_run.unique_id}-{self.test.serial_number}"
382-
)
383-
384-
environment['TMT_SOURCE_DIR'] = EnvVarValue(self.discover_phase.source_dir)
385-
386407
else:
387408
environment = self._environment
388409

389-
# TODO: this was owned by plan, but at wrong position, and it will
390-
# be owned by plan again once the dust of environment untangling
391-
# settles. Follow https://github.com/teemtee/tmt/issues/4241 for
392-
# more.
393-
environment['TMT_PLAN_ENVIRONMENT_FILE'] = EnvVarValue(self.guest.plan_environment_path)
394-
395-
environment.update(
396-
# Add variables from invocation contexts
397-
self.abort,
398-
self.reboot,
399-
self.restart,
400-
self.pidfile,
401-
self.restraint,
402-
# Add variables the framework wants to expose
403-
self.test.test_framework.get_environment_variables(self, self.logger),
404-
)
410+
environment.update(self.intrinsic_environment)
405411

406412
self._environment = environment
407413

tmt/steps/prepare/shell.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,6 @@ def go(
151151
self.step.plan.environment,
152152
)
153153

154-
# TODO: this was owned by plan, but at wrong position, and it will
155-
# be owned by plan again once the dust of environment untangling
156-
# settles. Follow https://github.com/teemtee/tmt/issues/4241 for
157-
# more.
158-
if guest.plan_environment_path:
159-
environment['TMT_PLAN_ENVIRONMENT_FILE'] = EnvVarValue(guest.plan_environment_path)
160-
161154
# Give a short summary
162155
overview = fmf.utils.listed(self.data.script, 'script')
163156
logger.info('overview', f'{overview} found', 'green')
@@ -269,8 +262,12 @@ def _invoke_script(
269262
script_log_filepath.touch()
270263

271264
script_environment = environment.copy()
272-
script_environment.update(reboot_context)
273-
script_environment.update(pidfile_context)
265+
script_environment.update(
266+
self.step.plan.intrinsic_environment,
267+
guest.intrinsic_environment,
268+
reboot_context.intrinsic_environment,
269+
pidfile_context.intrinsic_environment,
270+
)
274271

275272
pull_options = DEFAULT_PULL_OPTIONS.copy()
276273
pull_options.exclude.append(str(script_log_filepath))

tmt/steps/provision/local.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
Path,
2121
ShellScript,
2222
)
23-
from tmt.utils.environment import Environment, EnvVarValue
23+
from tmt.utils.environment import Environment
2424
from tmt.utils.hints import get_hint
2525
from tmt.utils.wait import Waiting
2626

@@ -73,14 +73,12 @@ def _prepare_command_environment(
7373
environment.update(self.environment)
7474

7575
if isinstance(self.parent, tmt.steps.Step):
76-
environment.update(self.parent.plan)
77-
78-
# TODO: this was owned by plan, but at wrong position, and it will
79-
# be owned by plan again once the dust of environment untangling
80-
# settles. Follow https://github.com/teemtee/tmt/issues/4241 for
81-
# more.
82-
if self.plan_environment_path:
83-
environment['TMT_PLAN_ENVIRONMENT_FILE'] = EnvVarValue(self.plan_environment_path)
76+
environment.update(self.parent.plan.environment)
77+
78+
environment.update(self.intrinsic_environment)
79+
80+
if isinstance(self.parent, tmt.steps.Step):
81+
environment.update(self.parent.plan.intrinsic_environment)
8482

8583
else:
8684
environment = super()._prepare_command_environment(environment=environment)

0 commit comments

Comments
 (0)