Skip to content

Commit

Permalink
Change location of ephemeral directory (#4362)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ssbarnea authored Jan 28, 2025
1 parent 2372b5a commit 45db06d
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 70 deletions.
56 changes: 10 additions & 46 deletions src/molecule/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
import os
import shutil

from functools import cached_property
from pathlib import Path
from time import sleep
from typing import TYPE_CHECKING

from molecule import scenarios, util
from molecule.constants import RC_TIMEOUT
from molecule.text import checksum


if TYPE_CHECKING:
Expand Down Expand Up @@ -119,7 +121,7 @@ def directory(self) -> str:
path = Path(self.config.molecule_file).parent
return str(path)

@property
@cached_property
def ephemeral_directory(self) -> str:
"""Acquire the ephemeral directory.
Expand All @@ -129,22 +131,17 @@ def ephemeral_directory(self) -> str:
Raises:
SystemExit: If lock cannot be acquired before timeout.
"""
path: str | Path | None = os.getenv("MOLECULE_EPHEMERAL_DIRECTORY", None)
if not path:
path: Path
if "MOLECULE_EPHEMERAL_DIRECTORY" not in os.environ:
project_directory = Path(self.config.project_directory).name

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

project_scenario_directory = Path(
self.config.cache_directory,
project_directory,
self.name,
)
path = ephemeral_directory(project_scenario_directory)

if isinstance(path, str):
path = Path(path)
project_scenario_directory = f"molecule.{checksum(project_directory, 4)}.{self.name}"
path = self.config.runtime.cache_dir / "tmp" / project_scenario_directory
else:
path = Path(os.getenv("MOLECULE_EPHEMERAL_DIRECTORY", ""))

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

return str(path)
return path.absolute().as_posix()

@property
def inventory_directory(self) -> str:
Expand Down Expand Up @@ -314,36 +311,3 @@ def _setup(self) -> None:
inventory = Path(self.inventory_directory)
if not inventory.is_dir():
inventory.mkdir(exist_ok=True, parents=True)


def ephemeral_directory(path: Path | None = None) -> Path:
"""Return temporary directory to be used by molecule.
Molecule users should not make any assumptions about its location,
permissions or its content as this may change in future release.
Args:
path: Ephemeral directory name.
Returns:
The full ephemeral directory path.
Raises:
RuntimeError: If ephemeral directory location cannot be determined
"""
d: str | Path | None = os.getenv("MOLECULE_EPHEMERAL_DIRECTORY")
if not d:
d = os.getenv("XDG_CACHE_HOME", Path("~/.cache").expanduser())
if not d:
msg = "Unable to determine ephemeral directory to use."
raise RuntimeError(msg)

if isinstance(d, str):
d = Path(d)
d = d.resolve() / (path if path else "molecule")

if not d.is_dir():
os.umask(0o077)
d.mkdir(mode=0o700, parents=True, exist_ok=True)

return d
22 changes: 22 additions & 0 deletions src/molecule/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

from __future__ import annotations

import base64
import hashlib
import re

from typing import TYPE_CHECKING


if TYPE_CHECKING:
from pathlib import Path
from typing import AnyStr


Expand Down Expand Up @@ -108,3 +111,22 @@ def _to_unicode(data: AnyStr) -> str:
if isinstance(data, bytes):
return data.decode("utf-8")
return data


def checksum(data: str | Path, length: int = 5) -> str:
"""Returns a checksum for the given data.
Args:
data: The data to checksum.
length: The length of the checksum.
Returns:
A checksum string.
"""
data = str(data)
# Hash the input string using SHA-256
hash_object = hashlib.sha256(data.encode("utf-8"))
# Convert the hash to a base64-encoded string
base64_hash = base64.urlsafe_b64encode(hash_object.digest()).decode("utf-8")
# Truncate the result to the desired length
return base64_hash[:length]
31 changes: 7 additions & 24 deletions tests/unit/test_scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import pytest

from molecule import config, util
from molecule.scenario import Scenario, ephemeral_directory
from molecule.scenario import Scenario


if TYPE_CHECKING:
Expand Down Expand Up @@ -235,12 +235,8 @@ def test_setup_creates_ephemeral_and_inventory_directories( # noqa: D103

assert Path(ephemeral_dir).is_dir()
assert Path(inventory_dir).is_dir()


def test_ephemeral_directory() -> None: # noqa: D103
# assure we can write to ephemeral directory
path = Path("foo/bar")
assert os.access(ephemeral_directory(path), os.W_OK)
assert os.access(ephemeral_dir, os.W_OK)


def test_ephemeral_directory_overridden_via_env_var(
Expand All @@ -255,22 +251,9 @@ def test_ephemeral_directory_overridden_via_env_var(
"""
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("MOLECULE_EPHEMERAL_DIRECTORY", "foo/bar")
scenario = Scenario(config.Config(""))

path = Path("foo/bar")
assert os.access(ephemeral_directory(path), os.W_OK)


def test_ephemeral_directory_overridden_via_env_var_uses_absolute_path(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""Confirm MOLECULE_EPHEMERAL_DIRECTORY uses absolute path.
Args:
monkeypatch: Pytest monkeypatch fixture.
tmp_path: Pytest tmp_path fixture.
"""
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("MOLECULE_EPHEMERAL_DIRECTORY", "foo/bar")

assert Path(ephemeral_directory()).is_absolute()
assert os.access(scenario.ephemeral_directory, os.W_OK)
# Confirm MOLECULE_EPHEMERAL_DIRECTORY uses absolute path.
assert Path(scenario.ephemeral_directory).is_absolute()
assert scenario.ephemeral_directory.endswith("foo/bar")
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ commands =
coverage xml --data-file={env:COVERAGE_COMBINED} -o {envdir}/coverage.xml --fail-under=0
coverage lcov --data-file={env:COVERAGE_COMBINED} -o {toxinidir}/.cache/.coverage/lcov.info --fail-under=0
coverage report --data-file={env:COVERAGE_COMBINED}
commands_post =
git clean -f -d
allowlist_externals =
git
rm
Expand Down

0 comments on commit 45db06d

Please sign in to comment.