shutil.rmtree() can fail when removing directories that contain
non-writable files or directories, even if they are owned by the
current user. Using TemporaryDirectory.cleanup() avoids this issue.
This problem can be reproduced with:
kas shell kas-project.yml -c 'go install
github.com/spf13/cobra-cli@latest'
When kas exits, cleanup of the temporary HOME directory may fail with
PermissionError:
Exception ignored in: <function SetupHome.__del__ at 0x74f6c6254040>
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/kas/libcmds.py", line 165, in __del__
shutil.rmtree(self.tmpdirname)
(snip)
PermissionError: [Errno 13] Permission denied: ‘go.mod'
This happens because `go install` creates read-only directories and
files under the HOME directory, which causes rmtree() to fail during
cleanup.
As a result, temporary data remains in /tmp after each kas execution,
potentially accumulating hundreds of megabytes over time.
Signed-off-by: Akihiro Yamazaki <
aki...@tinkermode.com>
---
kas/libcmds.py | 32 ++++++++++++++++----------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/kas/libcmds.py b/kas/libcmds.py
index 3a651bf..2c268fa 100644
--- a/kas/libcmds.py
+++ b/kas/libcmds.py
@@ -191,11 +191,11 @@ class SetupHome(Command):
def __init__(self):
super().__init__()
- self.tmpdirname = None
+ self.tmpdir = None
def __del__(self):
- if self.tmpdirname:
- shutil.rmtree(self.tmpdirname)
+ if self.tmpdir:
+ self.tmpdir.cleanup()
def __str__(self):
return 'setup_home'
@@ -232,10 +232,10 @@ class SetupHome(Command):
def _setup_netrc(self):
netrc_file = self._path_from_env('NETRC_FILE')
if netrc_file:
- shutil.copy(netrc_file, self.tmpdirname + "/.netrc")
+ shutil.copy(netrc_file,
self.tmpdir.name + "/.netrc")
if os.environ.get('CI_SERVER_HOST', False) \
and os.environ.get('CI_JOB_TOKEN', False):
- with open(self.tmpdirname + '/.netrc', 'a') as fds:
+ with open(
self.tmpdir.name + '/.netrc', 'a') as fds:
fds.write('machine ' + os.environ['CI_SERVER_HOST'] + '\n'
'login gitlab-ci-token\n'
'password ' + os.environ['CI_JOB_TOKEN'] + '\n')
@@ -244,22 +244,22 @@ class SetupHome(Command):
npmrc_file = self._path_from_env('NPMRC_FILE')
if not npmrc_file:
return
- shutil.copy(npmrc_file, self.tmpdirname + "/.npmrc")
+ shutil.copy(npmrc_file,
self.tmpdir.name + "/.npmrc")
def _setup_registry_auth(self):
- os.makedirs(self.tmpdirname + "/.docker")
+ os.makedirs(
self.tmpdir.name + "/.docker")
reg_auth_file = self._path_from_env('REGISTRY_AUTH_FILE')
if reg_auth_file:
shutil.copy(reg_auth_file,
- self.tmpdirname + "/.docker/config.json")
- elif not os.path.exists(self.tmpdirname + '/.docker/config.json'):
- with open(self.tmpdirname + '/.docker/config.json', 'w') as fds:
+
self.tmpdir.name + "/.docker/config.json")
+ elif not os.path.exists(
self.tmpdir.name + '/.docker/config.json'):
+ with open(
self.tmpdir.name + '/.docker/config.json', 'w') as fds:
fds.write("{}")
if os.environ.get('CI_REGISTRY', False) \
and os.environ.get('CI_JOB_TOKEN', False) \
and os.environ.get('CI_REGISTRY_USER', False):
- with open(self.tmpdirname + '/.docker/config.json', 'r+') as fds:
+ with open(
self.tmpdir.name + '/.docker/config.json', 'r+') as fds:
data = json.loads(fds.read())
token = os.environ['CI_JOB_TOKEN']
base64_token = base64.b64encode(token.encode()).decode()
@@ -272,7 +272,7 @@ class SetupHome(Command):
fds.truncate()
def _setup_aws_creds(self):
- aws_dir = self.tmpdirname + "/.aws"
+ aws_dir =
self.tmpdir.name + "/.aws"
conf_file = aws_dir + "/config"
shared_creds_file = aws_dir + "/credentials"
sso_cache_dir = aws_dir + "/sso/cache"
@@ -343,7 +343,7 @@ class SetupHome(Command):
def _setup_gitconfig(self):
gitconfig_host = self._path_from_env('GITCONFIG_FILE')
- gitconfig_kas = self.tmpdirname + '/.gitconfig'
+ gitconfig_kas =
self.tmpdir.name + '/.gitconfig'
# when running in a externally managed environment,
# always try to read the gitconfig
@@ -382,8 +382,8 @@ class SetupHome(Command):
managed_env = get_context().managed_env
if managed_env:
logging.info(f'Running on {managed_env}')
- if not self.tmpdirname:
- self.tmpdirname = tempfile.mkdtemp()
+ if not self.tmpdir:
+ self.tmpdir = tempfile.TemporaryDirectory()
def_umask = os.umask(0o077)
self._setup_netrc()
self._setup_npmrc()
@@ -392,7 +392,7 @@ class SetupHome(Command):
self._setup_aws_creds()
os.umask(def_umask)
- ctx.environ['HOME'] = self.tmpdirname
+ ctx.environ['HOME'] =
self.tmpdir.name
class MakeNonInteractive(Command):