33import hashlib
44import os
55import tempfile
6+ import warnings
67from pathlib import Path
78
89
10+ def is_writable (path : Path ) -> bool :
11+ """Check if path is writable, creating if necessary.
12+
13+ Args:
14+ path: Path to check.
15+
16+ Returns:
17+ True if path is writable, False otherwise.
18+ """
19+ try :
20+ path .mkdir (parents = True , exist_ok = True )
21+ except OSError :
22+ return False
23+ return path .exists () and os .access (path , os .W_OK )
24+
25+
926def get_cache_dir (project_dir : Path , * , isolated : bool = True ) -> Path :
1027 """Compute cache directory to be used based on project path.
1128
@@ -14,31 +31,51 @@ def get_cache_dir(project_dir: Path, *, isolated: bool = True) -> Path:
1431 isolated: Whether to use isolated cache directory.
1532
1633 Returns:
17- Cache directory path .
34+ A writable cache directory .
1835
1936 Raises:
2037 RuntimeError: if cache directory is not writable.
38+ OSError: if cache directory cannot be created.
2139 """
22- cache_dir = Path (os .environ .get ("ANSIBLE_HOME" , "~/.ansible" )).expanduser ()
23-
40+ cache_dir : Path | None = None
2441 if "VIRTUAL_ENV" in os .environ :
25- path = Path (os .environ ["VIRTUAL_ENV" ])
26- if not path . exists (): # pragma: no cover
27- msg = f"VIRTUAL_ENV= { os . environ [ 'VIRTUAL_ENV' ] } does not exist."
28- raise RuntimeError ( msg )
29- cache_dir = path . resolve () / ".ansible "
30- elif isolated :
31- if not project_dir . exists () or not os . access ( project_dir , os . W_OK ):
32- # As "project_dir" can also be "/" and user might not be able
33- # to write to it, we use a temporary directory as fallback.
34- checksum = hashlib . sha256 (
35- project_dir . as_posix (). encode ( "utf-8" ),
36- ). hexdigest ()[: 4 ]
37-
38- cache_dir = Path ( tempfile . gettempdir ()) / f".ansible- { checksum } "
39- cache_dir . mkdir ( parents = True , exist_ok = True )
42+ path = Path (os .environ ["VIRTUAL_ENV" ]). resolve () / ".ansible"
43+ if is_writable ( path ):
44+ cache_dir = path
45+ else :
46+ msg = f"Found VIRTUAL_ENV= { os . environ [ 'VIRTUAL_ENV' ] } but we cannot use it for caching as it is not writable. "
47+ warnings . warn (
48+ message = msg ,
49+ stacklevel = 2 ,
50+ source = { "msg" : msg },
51+ )
52+
53+ if isolated :
54+ project_dir = project_dir . resolve () / ".ansible"
55+ if is_writable ( project_dir ):
56+ cache_dir = project_dir
4057 else :
41- cache_dir = project_dir .resolve () / ".ansible"
58+ msg = f"Project directory { project_dir } cannot be used for caching as it is not writable."
59+ warnings .warn (msg , stacklevel = 2 )
60+ else :
61+ cache_dir = Path (os .environ .get ("ANSIBLE_HOME" , "~/.ansible" )).expanduser ()
62+ # This code should be never be reached because import from ansible-core
63+ # would trigger a fatal error if this location is not writable.
64+ if not is_writable (cache_dir ): # pragma: no cover
65+ msg = f"Cache directory { cache_dir } is not writable."
66+ raise OSError (msg )
67+
68+ if not cache_dir :
69+ # As "project_dir" can also be "/" and user might not be able
70+ # to write to it, we use a temporary directory as fallback.
71+ checksum = hashlib .sha256 (
72+ project_dir .as_posix ().encode ("utf-8" ),
73+ ).hexdigest ()[:4 ]
74+
75+ cache_dir = Path (tempfile .gettempdir ()) / f".ansible-{ checksum } "
76+ cache_dir .mkdir (parents = True , exist_ok = True )
77+ msg = f"Using unique temporary directory { cache_dir } for caching."
78+ warnings .warn (msg , stacklevel = 2 )
4279
4380 # Ensure basic folder structure exists so `ansible-galaxy list` does not
4481 # fail with: None of the provided paths were usable. Please specify a valid path with
@@ -49,4 +86,5 @@ def get_cache_dir(project_dir: Path, *, isolated: bool = True) -> Path:
4986 msg = "Failed to create cache directory."
5087 raise RuntimeError (msg ) from exc
5188
89+ # We succeed only if the path is writable.
5290 return cache_dir
0 commit comments