Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for XDG_CONFIG_HOME #129

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions b2sdk/account_info/sqlite_account_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,28 @@ class SqliteAccountInfo(UrlPoolAccountInfo):
completed.
"""

def __init__(self, file_name=None, last_upgrade_to_run=None):
def __init__(self, file_name=None, env=os.environ, last_upgrade_to_run=None):
"""
If ``file_name`` argument is empty or ``None``, path from ``B2_ACCOUNT_INFO`` environment variable is used. If that is not available, a default of ``~/.b2_account_info`` is used.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am reading this docstring over and over and I think apiver doesn't let us change the default location to use XDG_CONFIG_HOME in v1. Someone may have written a program that relies on the promise that when the filename was not passed and B2_ACCOUNT_INFO was not set, then the db file will land in ~/.b2_account_info. After running something, the program might clean the environment by removing that file.

If we make this change and XDG_CONFIG_HOME is defined, sdk user would not expect the configuration of the end user to be suddenly leaked (as it is not cleaned up anymore), possibly allowing access to the following user.

I think this is a good change and we should introduce it, but that should happen with the release of from b2sdk.v2 import *, otherwise we risk introducing a breaking change, while the purpose of apiver is to avoid it unless fixing a bug (which we are not doing here) which would change behavior because the old behavior was just wrong.

Furthermore, we should separate the documentation of the interface from the documentation of the current behavior, so that this can be altered in the future, by mentioning that the user should not define of this algorithm staying exactly the same, but should use self.filename to determine the actually determined location of the file in case it needs to be accessed later.

We did not have a compelling reason to release v2, but this is a good one.

@bwbeach @0az do you agree with my viewpoint? Shall we move it to v2?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch: I didn't actually think of that case.

Sounds good to me.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0az then would you please adjust the docstring here? We'll hold off merging this until b2sdk 1.1.0 and b2cli 2.0.0 are shipped, then we'll increment apiver to v2 and finally we'll merge your thing (though a final adjustment will be necessary to provide backwards compatibility)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another question for v2: should SqliteAccountInfo support :memory: or anonymous (empty string) databases?

https://www.sqlite.org/c3ref/open.html

If the filename is ":memory:", then a private, temporary in-memory database is created for the connection. This in-memory database will vanish when the database connection is closed. Future versions of SQLite might make use of additional special filenames that begin with the ":" character. It is recommended that when a database filename actually does begin with a ":" character you should prefix the filename with a pathname such as "./" to avoid ambiguity.

If the filename is an empty string, then a private, temporary on-disk database will be created. This private database will be automatically deleted as soon as the database connection is closed.

Copy link
Collaborator

@ppolewicz ppolewicz Jun 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like that could replace InMemoryAccountInfo with one line of code. I like less code.

If ``file_name`` argument is empty or ``None``, the path from
``B2_ACCOUNT_INFO`` environment variable is used. If that is not
available, we check if the default ``~/.b2_account_info`` exists to
ensure backwards compatibility, before using ``XDG_CONFIG_HOME``. If
``XDG_CONFIG_HOME`` is not set, we fall back to the default.

:param str file_name: The sqlite file to use; overrides the default.
:param dict env: Override Environment variables. For testing only.
:param int last_upgrade_to_run: For testing only, override the auto-update on the db.
"""
self.thread_local = threading.local()
user_account_info_path = file_name or os.environ.get(
B2_ACCOUNT_INFO_ENV_VAR, B2_ACCOUNT_INFO_DEFAULT_FILE
)
if B2_ACCOUNT_INFO_ENV_VAR in env:
user_account_info_path = env[B2_ACCOUNT_INFO_ENV_VAR]
elif 'XDG_CONFIG_HOME' in env and not os.path.exists(
os.path.expanduser(B2_ACCOUNT_INFO_DEFAULT_FILE)
):
user_account_info_path = os.path.join(env['XDG_CONFIG_HOME'], 'b2', 'account_info')
os.makedirs(os.path.join(env['XDG_CONFIG_HOME'], 'b2'), mode=0o755, exist_ok=True)
else:
user_account_info_path = B2_ACCOUNT_INFO_DEFAULT_FILE
self.filename = file_name or os.path.expanduser(user_account_info_path)
logger.debug('%s file path to use: %s', self.__class__.__name__, self.filename)
self._validate_database()
Expand Down
36 changes: 32 additions & 4 deletions test/v1/test_account_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from .test_base import TestBase

from .deps import AbstractAccountInfo, InMemoryAccountInfo, UploadUrlPool, SqliteAccountInfo
from .deps import B2_ACCOUNT_INFO_ENV_VAR, AbstractAccountInfo, InMemoryAccountInfo, UploadUrlPool, SqliteAccountInfo
from .deps_exception import CorruptAccountInfo, MissingAccountData

try:
Expand Down Expand Up @@ -292,11 +292,39 @@ def test_upgrade_2_default_app_key(self):
def _make_info(self):
return self._make_sqlite_account_info()

def _make_sqlite_account_info(self, last_upgrade_to_run=None):
def _make_sqlite_account_info(self, env=None, last_upgrade_to_run=None):
"""
Returns a new StoredAccountInfo that has just read the data from the file.
Returns a new SqliteAccountInfo that has just read the data from the file.
"""
return SqliteAccountInfo(file_name=self.db_path, last_upgrade_to_run=None)
return SqliteAccountInfo(
file_name=self.db_path if not env else None,
env=env or {},
last_upgrade_to_run=last_upgrade_to_run
)

def test_account_info_persistence(self):
self._test_account_info(check_persistence=True)

def test_uses_xdg_config_home(self):
with tempfile.TemporaryDirectory() as d:
account_info = self._make_sqlite_account_info(env={'XDG_CONFIG_HOME': d})
expected_path = os.path.abspath(os.path.join(d, 'b2', 'account_info'))
actual_path = os.path.abspath(account_info.filename)
assert expected_path == actual_path, 'Actual path %s is not equal to $XDG_CONFIG_HOME/b2/account_info'
assert os.path.exists(
os.path.join(d, 'b2')
), 'Config folder $XDG_CONFIG_HOME/b2 was not created!'

def test_account_info_env_var_overrides_xdg_config_home(self):
with tempfile.TemporaryDirectory() as d:
account_info = self._make_sqlite_account_info(
env={
'XDG_CONFIG_HOME': d,
B2_ACCOUNT_INFO_ENV_VAR: os.path.join(d, 'b2_account_info')
}
)
expected_path = os.path.abspath(os.path.join(d, 'b2_account_info'))
actual_path = os.path.abspath(account_info.filename)
assert expected_path == actual_path, 'Actual path {path} is not equal to ${var}'.format(
path=actual_path, var=B2_ACCOUNT_INFO_ENV_VAR
)