Skip to content

Commit 45db06d

Browse files
authored
Change location of ephemeral directory (#4362)
This change is related to https://ansible.readthedocs.io/projects/dev-tools/user-guide/test-isolation/ and makes molecule use the virtualenv for storing its ephemeral data, avoiding use of user level temp directory. Fixes: https://issues.redhat.com/browse/AAP-37862
1 parent 2372b5a commit 45db06d

File tree

4 files changed

+41
-70
lines changed

4 files changed

+41
-70
lines changed

src/molecule/scenario.py

+10-46
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@
2626
import os
2727
import shutil
2828

29+
from functools import cached_property
2930
from pathlib import Path
3031
from time import sleep
3132
from typing import TYPE_CHECKING
3233

3334
from molecule import scenarios, util
3435
from molecule.constants import RC_TIMEOUT
36+
from molecule.text import checksum
3537

3638

3739
if TYPE_CHECKING:
@@ -119,7 +121,7 @@ def directory(self) -> str:
119121
path = Path(self.config.molecule_file).parent
120122
return str(path)
121123

122-
@property
124+
@cached_property
123125
def ephemeral_directory(self) -> str:
124126
"""Acquire the ephemeral directory.
125127
@@ -129,22 +131,17 @@ def ephemeral_directory(self) -> str:
129131
Raises:
130132
SystemExit: If lock cannot be acquired before timeout.
131133
"""
132-
path: str | Path | None = os.getenv("MOLECULE_EPHEMERAL_DIRECTORY", None)
133-
if not path:
134+
path: Path
135+
if "MOLECULE_EPHEMERAL_DIRECTORY" not in os.environ:
134136
project_directory = Path(self.config.project_directory).name
135137

136138
if self.config.is_parallel:
137139
project_directory = f"{project_directory}-{self.config._run_uuid}" # noqa: SLF001
138140

139-
project_scenario_directory = Path(
140-
self.config.cache_directory,
141-
project_directory,
142-
self.name,
143-
)
144-
path = ephemeral_directory(project_scenario_directory)
145-
146-
if isinstance(path, str):
147-
path = Path(path)
141+
project_scenario_directory = f"molecule.{checksum(project_directory, 4)}.{self.name}"
142+
path = self.config.runtime.cache_dir / "tmp" / project_scenario_directory
143+
else:
144+
path = Path(os.getenv("MOLECULE_EPHEMERAL_DIRECTORY", ""))
148145

149146
if os.environ.get("MOLECULE_PARALLEL", False) and not self._lock:
150147
lock_file = path / ".lock"
@@ -165,7 +162,7 @@ def ephemeral_directory(self) -> str:
165162
LOG.warning("Timedout trying to acquire lock on %s", path)
166163
raise SystemExit(RC_TIMEOUT)
167164

168-
return str(path)
165+
return path.absolute().as_posix()
169166

170167
@property
171168
def inventory_directory(self) -> str:
@@ -314,36 +311,3 @@ def _setup(self) -> None:
314311
inventory = Path(self.inventory_directory)
315312
if not inventory.is_dir():
316313
inventory.mkdir(exist_ok=True, parents=True)
317-
318-
319-
def ephemeral_directory(path: Path | None = None) -> Path:
320-
"""Return temporary directory to be used by molecule.
321-
322-
Molecule users should not make any assumptions about its location,
323-
permissions or its content as this may change in future release.
324-
325-
Args:
326-
path: Ephemeral directory name.
327-
328-
Returns:
329-
The full ephemeral directory path.
330-
331-
Raises:
332-
RuntimeError: If ephemeral directory location cannot be determined
333-
"""
334-
d: str | Path | None = os.getenv("MOLECULE_EPHEMERAL_DIRECTORY")
335-
if not d:
336-
d = os.getenv("XDG_CACHE_HOME", Path("~/.cache").expanduser())
337-
if not d:
338-
msg = "Unable to determine ephemeral directory to use."
339-
raise RuntimeError(msg)
340-
341-
if isinstance(d, str):
342-
d = Path(d)
343-
d = d.resolve() / (path if path else "molecule")
344-
345-
if not d.is_dir():
346-
os.umask(0o077)
347-
d.mkdir(mode=0o700, parents=True, exist_ok=True)
348-
349-
return d

src/molecule/text.py

+22
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
from __future__ import annotations
44

5+
import base64
6+
import hashlib
57
import re
68

79
from typing import TYPE_CHECKING
810

911

1012
if TYPE_CHECKING:
13+
from pathlib import Path
1114
from typing import AnyStr
1215

1316

@@ -108,3 +111,22 @@ def _to_unicode(data: AnyStr) -> str:
108111
if isinstance(data, bytes):
109112
return data.decode("utf-8")
110113
return data
114+
115+
116+
def checksum(data: str | Path, length: int = 5) -> str:
117+
"""Returns a checksum for the given data.
118+
119+
Args:
120+
data: The data to checksum.
121+
length: The length of the checksum.
122+
123+
Returns:
124+
A checksum string.
125+
"""
126+
data = str(data)
127+
# Hash the input string using SHA-256
128+
hash_object = hashlib.sha256(data.encode("utf-8"))
129+
# Convert the hash to a base64-encoded string
130+
base64_hash = base64.urlsafe_b64encode(hash_object.digest()).decode("utf-8")
131+
# Truncate the result to the desired length
132+
return base64_hash[:length]

tests/unit/test_scenario.py

+7-24
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import pytest
2929

3030
from molecule import config, util
31-
from molecule.scenario import Scenario, ephemeral_directory
31+
from molecule.scenario import Scenario
3232

3333

3434
if TYPE_CHECKING:
@@ -235,12 +235,8 @@ def test_setup_creates_ephemeral_and_inventory_directories( # noqa: D103
235235

236236
assert Path(ephemeral_dir).is_dir()
237237
assert Path(inventory_dir).is_dir()
238-
239-
240-
def test_ephemeral_directory() -> None: # noqa: D103
241238
# assure we can write to ephemeral directory
242-
path = Path("foo/bar")
243-
assert os.access(ephemeral_directory(path), os.W_OK)
239+
assert os.access(ephemeral_dir, os.W_OK)
244240

245241

246242
def test_ephemeral_directory_overridden_via_env_var(
@@ -255,22 +251,9 @@ def test_ephemeral_directory_overridden_via_env_var(
255251
"""
256252
monkeypatch.chdir(tmp_path)
257253
monkeypatch.setenv("MOLECULE_EPHEMERAL_DIRECTORY", "foo/bar")
254+
scenario = Scenario(config.Config(""))
258255

259-
path = Path("foo/bar")
260-
assert os.access(ephemeral_directory(path), os.W_OK)
261-
262-
263-
def test_ephemeral_directory_overridden_via_env_var_uses_absolute_path(
264-
monkeypatch: pytest.MonkeyPatch,
265-
tmp_path: Path,
266-
) -> None:
267-
"""Confirm MOLECULE_EPHEMERAL_DIRECTORY uses absolute path.
268-
269-
Args:
270-
monkeypatch: Pytest monkeypatch fixture.
271-
tmp_path: Pytest tmp_path fixture.
272-
"""
273-
monkeypatch.chdir(tmp_path)
274-
monkeypatch.setenv("MOLECULE_EPHEMERAL_DIRECTORY", "foo/bar")
275-
276-
assert Path(ephemeral_directory()).is_absolute()
256+
assert os.access(scenario.ephemeral_directory, os.W_OK)
257+
# Confirm MOLECULE_EPHEMERAL_DIRECTORY uses absolute path.
258+
assert Path(scenario.ephemeral_directory).is_absolute()
259+
assert scenario.ephemeral_directory.endswith("foo/bar")

tox.ini

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ commands =
4848
coverage xml --data-file={env:COVERAGE_COMBINED} -o {envdir}/coverage.xml --fail-under=0
4949
coverage lcov --data-file={env:COVERAGE_COMBINED} -o {toxinidir}/.cache/.coverage/lcov.info --fail-under=0
5050
coverage report --data-file={env:COVERAGE_COMBINED}
51+
commands_post =
52+
git clean -f -d
5153
allowlist_externals =
5254
git
5355
rm

0 commit comments

Comments
 (0)