Skip to content

Commit

Permalink
Enforce posix paths for config files (#151)
Browse files Browse the repository at this point in the history
* enforce posix paths when saving and loading config file

* handle missing path keys in load_config

* test Respository.save_config for posix paths on windows

* test Repository.load_config with windows paths on posix systems
  • Loading branch information
dennisvang authored Jun 11, 2024
1 parent c65e34d commit 3d5f593
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 4 deletions.
14 changes: 11 additions & 3 deletions src/tufup/repo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,9 @@ def save_config(self):
"""Save current configuration."""
config_file_path = self.get_config_file_path()
# make paths relative to current working directory (cwd),
# if possible, otherwise keep absolute paths (note, to avoid
# confusion, using paths other than cwd is discouraged)
# if possible, otherwise keep absolute paths (see #50)
# (note, to avoid confusion, using paths other than cwd is discouraged)
# also enforce posix paths on windows (see #147)
temp_config_dict = self.config_dict # note self.config_dict is a property
for key in ['repo_dir', 'keys_dir']:
try:
Expand All @@ -605,13 +606,14 @@ def save_config(self):
# which are truncated with a tilde,
# e.g. c:\Users\RUNNER~1\...
pathlib.Path.cwd().resolve()
)
).as_posix()
except ValueError:
logger.warning(
f'Saving *absolute* path to config, because the path'
f' ({temp_config_dict[key]}) is not relative to cwd'
f' ({pathlib.Path.cwd()})'
)
temp_config_dict[key] = temp_config_dict[key].as_posix()
# write file
config_file_path.write_text(
data=json.dumps(temp_config_dict, default=str, sort_keys=True, indent=4),
Expand All @@ -629,6 +631,12 @@ def load_config(cls) -> dict:
logger.warning(f'config file not found: {file_path}')
except json.JSONDecodeError:
logger.warning(f'config file invalid: {file_path}')
# enforce posix paths (for legacy windows configs loaded on linux, see #147)
for key in ['repo_dir', 'keys_dir']:
value = config_dict.get(key)
if value:
# interpret the value as a windows path (\\ or /) then enforce posix (/)
config_dict[key] = pathlib.PureWindowsPath(value).as_posix()
return config_dict

@classmethod
Expand Down
31 changes: 30 additions & 1 deletion tests/test_repo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import copy
import tarfile
import unittest
from datetime import date, datetime, timedelta
import json
import logging
Expand Down Expand Up @@ -37,7 +38,7 @@
SUFFIX_PUB,
SUFFIX_PATCH,
)

from tufup.utils.platform_specific import ON_WINDOWS

mock_input = Mock(return_value='')

Expand Down Expand Up @@ -545,6 +546,21 @@ def test_save_config(self):
# note Path.is_relative_to() is introduced in python 3.9
self.assertFalse(pathlib.Path(config_dict[key]).is_absolute())

@unittest.skipUnless(condition=ON_WINDOWS, reason='windows only')
def test_save_config_windows_paths(self):
# prepare
kwargs = dict(repo_dir='foo\\repo', keys_dir='bar\\keys')
repo = Repository(app_name='test', **kwargs)
# test
repo.save_config()
self.assertTrue(repo.get_config_file_path().exists())
config_text = repo.get_config_file_path().read_text()
print(config_text)
config = json.loads(repo.get_config_file_path().read_text())
for key in kwargs.keys():
with self.subTest(msg=key):
self.assertEqual(kwargs[key].replace('\\', '/'), config[key])

def test_load_config(self):
# file does not exist
self.assertEqual(dict(), Repository.load_config())
Expand All @@ -553,6 +569,19 @@ def test_load_config(self):
# test
self.assertEqual(dict(), Repository.load_config())

@unittest.skipIf(condition=ON_WINDOWS, reason='posix only')
def test_load_config_windows_paths(self):
# prepare (mix windows paths and posix paths for convenience)
mock_config = dict(repo_dir='foo\\repo', keys_dir='/tmp/bar/keys')
config_path = Repository.get_config_file_path()
config_path.write_text(json.dumps(mock_config))
print(config_path.read_text())
# test
config = Repository.load_config()
for key in mock_config.keys():
with self.subTest(msg=key):
self.assertEqual(mock_config[key].replace('\\', '/'), config[key])

def test_from_config(self):
temp_dir = self.temp_dir_path.resolve()
repo_dir_abs = temp_dir / 'repo'
Expand Down

0 comments on commit 3d5f593

Please sign in to comment.