"""Credential support for ``AnnexRepo.enable_remote()`` and ``siblings enable``
Supported targets for automatic credential deployments are determined
by ``needs_specialremote_credential_envpatch()``. At the time of this
writing this includes the git-annex built-in remote types ``webdav``,
``s3``, and ``glacier``.
This patch also changes the function to raise its custom exception
with the context of an original underlying exception for better
error reporting.
"""
import logging
import os
import re
from unittest.mock import patch
from datalad.support.exceptions import (
AccessDeniedError,
AccessFailedError,
)
from datalad_next.exceptions import (
CommandError,
)
from datalad_next.utils import (
CredentialManager,
ensure_list,
get_specialremote_credential_envpatch,
get_specialremote_param_dict,
get_specialremote_credential_properties,
needs_specialremote_credential_envpatch,
)
from . import apply_patch
# reuse logger from -core, despite the unconventional name
lgr = logging.getLogger('datalad.annex')
# This function is taken from datalad-core@2ed709613ecde8218a215dcb7d74b4a352825685
# datalad/support/annexrepo.py:AnnexRepo
[docs]
def annexRepo__enable_remote(self, name, options=None, env=None):
"""Enables use of an existing special remote
Parameters
----------
name: str
name, the special remote was created with
options: list, optional
"""
# MIH thinks there should be no `env` argument at all
# https://github.com/datalad/datalad/issues/5162
# if it would not be there, this whole dance is pretty much
# obsolete
env = env or self._git_runner.env
# an enableremote can do pretty much anything, including a type change.
# in order to be able to determine whether credentials *will* be needed,
# we have to look ahead and form the special remote parameters that will
# be there at the end -- more or less
# pull info for present config
sp_remotes = {v['name']: dict(v, uuid=k) for k, v in self.get_special_remotes().items()}
remote_info = sp_remotes.get(name, {})
# TODO if remote_info is empty, we can fail right here
if options:
# and now update with given params
remote_info.update(get_specialremote_param_dict(options))
# careful here, `siblings()` also calls this for regular remotes, check
# for a known type
if 'type' in remote_info \
and needs_specialremote_credential_envpatch(remote_info['type']):
# see if we can identify any matching credentials
credprops = get_specialremote_credential_properties(remote_info)
credman = None
credspec = None
if credprops:
credman = CredentialManager(self.config)
creds = credman.query(_sortby='last-used', **credprops)
if creds:
# found one
credspec = creds[0]
# TODO manual entry could be supported here too! (also see at the end)
if env:
env.copy()
if credspec:
credpatch = get_specialremote_credential_envpatch(
remote_info['type'], credspec[1])
if credpatch:
if not env:
env = os.environ.copy()
env.update(credpatch)
try:
with patch.object(self._git_runner, 'env', env):
# TODO: outputs are nohow used/displayed. Eventually convert to
# to a generator style yielding our "dict records"
self.call_annex(['enableremote', name] + ensure_list(options))
except CommandError as e:
if re.match(r'.*StatusCodeException.*statusCode = 401', e.stderr):
raise AccessDeniedError(e.stderr) from e
elif 'FailedConnectionException' in e.stderr:
raise AccessFailedError(e.stderr) from e
else:
raise e
self.config.reload()
# TODO when manual credential entry is supported,
# implement store-after-success here
apply_patch(
'datalad.support.annexrepo', 'AnnexRepo', 'enable_remote',
annexRepo__enable_remote,
msg='Apply datalad-next patch to annexrepo.py:AnnexRepo.enable_remote')