Repository: iterative/dvc

Issue:
conf: credentialpath is not recalculated when it's a relpath
This one supposed to be relative to config file and converted to absolute and back on config load/save. This is done for `cache.dir`, local urls and `gdrive_user_credentials_file`, but not for `credentialpath`, which is used in s3 and gs.

Example: `cache.dir` - https://dvc.org/doc/command-reference/cache/dir#description. 





Discussion:
@efiop @Suor can I work on this issue? I'll probably need a bit more clarification as well
@efiop I started looking into this.
@damakuno thanks 🙏 to clarify, we discussed this with @godfryd offline a while ago and he had agreed to take a look. Could you please find another ticket? Let me know if you need my help on this.
@shcheklein alright,  I'll look for another ticket then 👍 

Resolving patch:
diff --git a/dvc/config.py b/dvc/config.py
--- a/dvc/config.py
+++ b/dvc/config.py
@@ -377,6 +377,12 @@ def _load_paths(conf, filename):
         def resolve(path):
             if os.path.isabs(path) or re.match(r"\w+://", path):
                 return path
+
+            # on windows convert slashes to backslashes
+            # to have path compatible with abs_conf_dir
+            if os.path.sep == "\\" and "/" in path:
+                path = path.replace("/", "\\")
+
             return RelPath(os.path.join(abs_conf_dir, path))
 
         return Config._map_dirs(conf, resolve)
@@ -403,7 +409,15 @@ def _map_dirs(conf, func):
         dirs_schema = {
             "cache": {"dir": func},
             "remote": {
-                str: {"url": func, "gdrive_user_credentials_file": func}
+                str: {
+                    "url": func,
+                    "gdrive_user_credentials_file": func,
+                    "gdrive_service_account_p12_file_path": func,
+                    "credentialpath": func,
+                    "keyfile": func,
+                    "cert_path": func,
+                    "key_path": func,
+                }
             },
         }
         return Schema(dirs_schema, extra=ALLOW_EXTRA)(conf)


Test patch:
diff --git a/tests/func/remote/test_gdrive.py b/tests/func/remote/test_gdrive.py
deleted file mode 100644
--- a/tests/func/remote/test_gdrive.py
+++ /dev/null
@@ -1,55 +0,0 @@
-import os
-import posixpath
-
-import configobj
-
-from dvc.main import main
-from dvc.repo import Repo
-from dvc.tree.gdrive import GDriveTree
-
-
-def test_relative_user_credentials_file_config_setting(tmp_dir, dvc):
-    # CI sets it to test GDrive, here we want to test the work with file system
-    # based, regular credentials
-    if os.getenv(GDriveTree.GDRIVE_CREDENTIALS_DATA):
-        del os.environ[GDriveTree.GDRIVE_CREDENTIALS_DATA]
-
-    credentials = os.path.join("secrets", "credentials.json")
-
-    # GDriveTree.credentials_location helper checks for file existence,
-    # create the file
-    tmp_dir.gen(credentials, "{'token': 'test'}")
-
-    remote_name = "gdrive"
-    assert (
-        main(["remote", "add", "-d", remote_name, "gdrive://root/test"]) == 0
-    )
-    assert (
-        main(
-            [
-                "remote",
-                "modify",
-                remote_name,
-                "gdrive_user_credentials_file",
-                credentials,
-            ]
-        )
-        == 0
-    )
-
-    # We need to load repo again to test updates to the config
-    str_path = os.fspath(tmp_dir)
-    repo = Repo(str_path)
-
-    # Check that in config we got the path relative to the config file itself
-    # Also, we store posix path even on Windows
-    config = configobj.ConfigObj(repo.config.files["repo"])
-    assert config[f'remote "{remote_name}"'][
-        "gdrive_user_credentials_file"
-    ] == posixpath.join("..", "secrets", "credentials.json")
-
-    # Check that in the remote itself we got an absolute path
-    remote = repo.cloud.get_remote(remote_name)
-    assert os.path.normpath(remote.tree.credentials_location) == os.path.join(
-        str_path, credentials
-    )
diff --git a/tests/func/test_config.py b/tests/func/test_config.py
--- a/tests/func/test_config.py
+++ b/tests/func/test_config.py
@@ -1,3 +1,5 @@
+import os
+
 import configobj
 import pytest
 
@@ -112,3 +114,34 @@ def test_merging_two_levels(dvc):
 def test_config_loads_without_error_for_non_dvc_repo(tmp_dir):
     # regression testing for https://github.com/iterative/dvc/issues/3328
     Config(validate=True)
+
+
+@pytest.mark.parametrize(
+    "field, remote_url",
+    [
+        ("credentialpath", "s3://mybucket/my/path"),
+        ("credentialpath", "gs://my-bucket/path"),
+        ("keyfile", "ssh://user@example.com:1234/path/to/dir"),
+        ("cert_path", "webdavs://example.com/files/USERNAME/"),
+        ("key_path", "webdavs://example.com/files/USERNAME/"),
+        ("gdrive_service_account_p12_file_path", "gdrive://root/test"),
+        ("gdrive_user_credentials_file", "gdrive://root/test"),
+    ],
+)
+def test_load_relative_paths(dvc, field, remote_url):
+    # set field to test
+    with dvc.config.edit() as conf:
+        conf["remote"]["test"] = {"url": remote_url, field: "file.txt"}
+
+    # check if written paths are correct
+    dvc_dir = dvc.config.dvc_dir
+    assert dvc.config["remote"]["test"][field] == os.path.join(
+        dvc_dir, "..", "file.txt"
+    )
+
+    # load config and check that it contains what we expect
+    # (relative paths are evaluated correctly)
+    cfg = Config(dvc_dir)
+    assert cfg["remote"]["test"][field] == os.path.join(
+        dvc_dir, "..", "file.txt"
+    )