Repository: iterative/dvc

Issue:
dvc version: show external cache and remotes being used
This would have made my life easier when investigating https://github.com/iterative/dvc/pull/4570.

Another question to ask is that `dvc version` can only have a limited amount of information. Should there be `dvc version --json`?




Discussion:

Patch:
diff --git a/dvc/info.py b/dvc/info.py
--- a/dvc/info.py
+++ b/dvc/info.py
@@ -8,7 +8,7 @@
 from dvc.repo import Repo
 from dvc.scm.base import SCMError
 from dvc.system import System
-from dvc.tree import TREES
+from dvc.tree import TREES, get_tree_cls, get_tree_config
 from dvc.utils import error_link
 from dvc.utils.pkg import PKG
 from dvc.version import __version__
@@ -50,6 +50,10 @@ def get_dvc_info():
         else:
             info.append("Cache types: " + error_link("no-dvc-cache"))
 
+        info.append(f"Caches: {_get_caches(repo.cache)}")
+
+        info.append(f"Remotes: {_get_remotes(repo.config)}")
+
     except NotDvcRepoError:
         pass
     except SCMError:
@@ -63,6 +67,26 @@ def get_dvc_info():
     return "\n".join(info)
 
 
+def _get_caches(cache):
+    caches = (
+        cache_type
+        for cache_type, cache_instance in cache.by_scheme()
+        if cache_instance
+    )
+
+    # Caches will be always non-empty including the local cache
+    return ", ".join(caches)
+
+
+def _get_remotes(config):
+    schemes = (
+        get_tree_cls(get_tree_config(config, name=remote)).scheme
+        for remote in config["remote"]
+    )
+
+    return ", ".join(schemes) or "None"
+
+
 def _get_linktype_support_info(repo):
 
     links = {
diff --git a/dvc/tree/__init__.py b/dvc/tree/__init__.py
--- a/dvc/tree/__init__.py
+++ b/dvc/tree/__init__.py
@@ -32,23 +32,23 @@
 ]
 
 
-def _get_tree(remote_conf):
+def get_tree_cls(remote_conf):
     for tree_cls in TREES:
         if tree_cls.supported(remote_conf):
             return tree_cls
     return LocalTree
 
 
-def _get_conf(repo, **kwargs):
+def get_tree_config(config, **kwargs):
     name = kwargs.get("name")
     if name:
-        remote_conf = repo.config["remote"][name.lower()]
+        remote_conf = config["remote"][name.lower()]
     else:
         remote_conf = kwargs
-    return _resolve_remote_refs(repo, remote_conf)
+    return _resolve_remote_refs(config, remote_conf)
 
 
-def _resolve_remote_refs(repo, remote_conf):
+def _resolve_remote_refs(config, remote_conf):
     # Support for cross referenced remotes.
     # This will merge the settings, shadowing base ref with remote_conf.
     # For example, having:
@@ -74,7 +74,7 @@ def _resolve_remote_refs(repo, remote_conf):
     if parsed.scheme != "remote":
         return remote_conf
 
-    base = _get_conf(repo, name=parsed.netloc)
+    base = get_tree_config(config, name=parsed.netloc)
     url = posixpath.join(base["url"], parsed.path.lstrip("/"))
     return {**base, **remote_conf, "url": url}
 
@@ -82,9 +82,9 @@ def _resolve_remote_refs(repo, remote_conf):
 def get_cloud_tree(repo, **kwargs):
     from dvc.config import SCHEMA, ConfigError, Invalid
 
-    remote_conf = _get_conf(repo, **kwargs)
+    remote_conf = get_tree_config(repo.config, **kwargs)
     try:
         remote_conf = SCHEMA["remote"][str](remote_conf)
     except Invalid as exc:
         raise ConfigError(str(exc)) from None
-    return _get_tree(remote_conf)(repo, remote_conf)
+    return get_tree_cls(remote_conf)(repo, remote_conf)


Test patch:
diff --git a/tests/func/test_version.py b/tests/func/test_version.py
--- a/tests/func/test_version.py
+++ b/tests/func/test_version.py
@@ -11,4 +11,6 @@ def test_(tmp_dir, dvc, scm, caplog):
     assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", caplog.text)
     assert re.search(r"Supports: .*", caplog.text)
     assert re.search(r"Cache types: .*", caplog.text)
+    assert re.search(r"Caches: local", caplog.text)
+    assert re.search(r"Remotes: None", caplog.text)
     assert "Repo: dvc, git" in caplog.text
diff --git a/tests/unit/test_info.py b/tests/unit/test_info.py
--- a/tests/unit/test_info.py
+++ b/tests/unit/test_info.py
@@ -53,6 +53,38 @@ def test_info_in_broken_git_repo(tmp_dir, dvc, scm, caplog):
     assert "Repo: dvc, git (broken)" in dvc_info
 
 
+def test_caches(tmp_dir, dvc, caplog):
+    tmp_dir.add_remote(
+        name="sshcache", url="ssh://example.com/path", default=False
+    )
+    with tmp_dir.dvc.config.edit() as conf:
+        conf["cache"]["ssh"] = "sshcache"
+
+    dvc_info = get_dvc_info()
+
+    # Order of cache types is runtime dependent
+    assert re.search("Caches: (local, ssh|ssh, local)", dvc_info)
+
+
+def test_remotes_empty(tmp_dir, dvc, caplog):
+    # No remotes are configured
+    dvc_info = get_dvc_info()
+
+    assert "Remotes: None" in dvc_info
+
+
+def test_remotes(tmp_dir, dvc, caplog):
+    tmp_dir.add_remote(name="server", url="ssh://localhost", default=False)
+    tmp_dir.add_remote(
+        name="r1", url="azure://example.com/path", default=False
+    )
+    tmp_dir.add_remote(name="r2", url="remote://server/path", default=False)
+
+    dvc_info = get_dvc_info()
+
+    assert re.search("Remotes: (ssh, azure|azure, ssh)", dvc_info)
+
+
 @pytest.mark.skipif(psutil is None, reason="No psutil.")
 def test_fs_info_in_repo(tmp_dir, dvc, caplog):
     os.mkdir(dvc.cache.local.cache_dir)