Repository: conan-io/conan
Pull request: Disabling/enabling remotes: Issue/5544 
Changelog: Feature: Add subcommand for enabling and disabling remotes
Docs: https://github.com/conan-io/docs/pull/1392

This PR provides the option to enable or disable remotes with the subcommand. By default all remotes are created with `disabled=False` and the `remotes.json` file only adds the information in case `disabled=True` so the absence of the parameter does not break loading files from previous versions

The use is:

`conan remote enable/disable -r remote_name_or_pattern`

This way you can have remotes in your `remotes.json` and skip them for operations such as
`search` or `install` without having to remove them. The command works with patterns as well so if you have lots of remotes and you only want to have a couple enabled you can do:

```
conan remote disable *
conan remote enable remote1
conan remote enable remote2
```

The default behaviour is that disabled remotes will be omitted for every operation but not for printing the list of remotes with `conan remote list` also if you try to make an operation with a remote that is in disabled state an error will be raised.

Closes #5544

- [x] Refer to the issue that supports this Pull Request.
- [x] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [x] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've followed the PEP8 style guides for Python code.
- [x] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one. 

<sup>**Note:** By default this PR will skip the slower tests and will use a limited set of python versions. Check [here](https://github.com/conan-io/conan/blob/develop/.github/PR_INCREASE_TESTING.md) how to increase the testing level by writing some tags in the current PR body text.</sup>


Problem statement:
Disabling/enabling remotes
A feature request from user could be summarized as:

- They have multiple remotes, 20+, that are defined and installed via ``conan config install``
- Developers need to login only to a few of them 1, 2...
- The remotes that the user is not logged in should be skipped.


After discussing it, the most common use case is that users would like to use those remotes, even if not logged-in, because anonymous read usage is common practice. So it doesn't seem a pattern that would be useful for other users.

Furthermore, it would be coupling the auth process with the remote enabling process, seems not the best way.

I think that the feature that could make sense and totally address this use case too is the one of enabling/disabling remotes:

- Remotes can be ``conan remote disable <remote-name>`` or ``conan remote enable <remote-name>``
- Disabled remotes are NOT removed from the definition, that means that packages that were installed from that remote do not lose their tracking to that remote (removing a remote cleans from the metadata the original remote)
- Disabled remotes are skipped when a package install is iterating remotes
- An exception is raised if a package tries to reach that remote (like --update, or completing missing sources)

This is a feature I have missed myself a few times, like disabling conan-center or bincrafters for doing experiments, and later needing to look up the remote to be able to set-up it again, and also playing with --insert to get the same order it was before. Plus it seems not very complex to implement.

Hints from issue discussion:
So you config install 20+ repositories and you would need to disable 18? I don't like it... I'm that case I think it is better to remove them or not install them.
Maybe allowing patterns for enable/disable is a bit more handy. You can disable * and enable two of them.
I think it is useful when I have artifactory configured, but I don't want to run it all the time. So to avoid any error when installing a package, I need to exclude it from my remote list, otherwise Conan will raise an error because it's impossible to reach Artifactory. Disabling it would be better.
Yes, for disabling multiple repos:

- Allowing patterns
- allowing to define the disable/enable state in remotes.txt files too

I think the first should be easier to implement

Commit message: test install with --update

Patch to review:
diff --git a/conans/client/cache/remote_registry.py b/conans/client/cache/remote_registry.py
index 4b5dc971883..4fee4f96f59 100644
--- a/conans/client/cache/remote_registry.py
+++ b/conans/client/cache/remote_registry.py
@@ -1,3 +1,4 @@
+import fnmatch
 import json
 import os
 from collections import OrderedDict, namedtuple
@@ -9,7 +10,7 @@
 from conans.model.ref import PackageReference, ConanFileReference
 
 
-Remote = namedtuple("Remote", "name url verify_ssl")
+Remote = namedtuple("Remote", "name url verify_ssl disabled")
 
 
 def load_registry_txt(contents):
@@ -106,7 +107,7 @@ def __init__(self):
     @classmethod
     def defaults(cls):
         result = Remotes()
-        result._remotes["conan-center"] = Remote("conan-center", "https://conan.bintray.com", True)
+        result._remotes["conan-center"] = Remote("conan-center", "https://conan.bintray.com", True, False)
         return result
 
     def select(self, remote_name):
@@ -122,29 +123,44 @@ def clear(self):
         self._remotes.clear()
 
     def items(self):
-        return self._remotes.items()
+        return OrderedDict(
+            (key, value) for (key, value) in self._remotes.items() if not value.disabled)
 
     def values(self):
+        return [value for value in self._remotes.values() if not value.disabled]
+
+    def all_values(self):
         return self._remotes.values()
 
+    def all_items(self):
+        return self._remotes.items()
+
     @staticmethod
     def loads(text):
         result = Remotes()
         data = json.loads(text)
         for r in data.get("remotes", []):
-            result._remotes[r["name"]] = Remote(r["name"], r["url"], r["verify_ssl"])
+            disabled = r.get("disabled", False)
+            result._remotes[r["name"]] = Remote(r["name"], r["url"],
+                                                r["verify_ssl"], disabled)
 
         return result
 
     def dumps(self):
         result = []
         for remote in self._remotes.values():
-            result.append("%s: %s [Verify SSL: %s]" % (remote.name, remote.url, remote.verify_ssl))
+            disabled_str = ", Disabled: True" if remote.disabled else ""
+            result.append("%s: %s [Verify SSL: %s%s]" %
+                          (remote.name, remote.url, remote.verify_ssl, disabled_str))
         return "\n".join(result)
 
     def save(self, filename):
-        ret = {"remotes": [{"name": r, "url": u, "verify_ssl": v}
-                           for r, (_, u, v) in self._remotes.items()]}
+        ret = {"remotes": []}
+        for r, (_, u, v, d) in self._remotes.items():
+            remote = {"name": r, "url": u, "verify_ssl": v}
+            if d:
+                remote["disabled"] = True
+            ret["remotes"].append(remote)
         save(filename, json.dumps(ret, indent=True))
 
     def _get_by_url(self, url):
@@ -154,12 +170,29 @@ def _get_by_url(self, url):
 
     def rename(self, remote_name, new_remote_name):
         if new_remote_name in self._remotes:
-            raise ConanException("Remote '%s' already exists" % new_remote_name)
+            raise ConanException("Remote '%s' already exists" %
+                                 new_remote_name)
+        elif self._remotes[remote_name].disabled:
+            raise ConanException("Remote '%s' is disabled" % remote_name)
 
         remote = self._remotes[remote_name]
-        new_remote = Remote(new_remote_name, remote.url, remote.verify_ssl)
-        self._remotes = OrderedDict([(new_remote_name, new_remote) if k == remote_name
-                                     else (k, v) for k, v in self._remotes.items()])
+        new_remote = Remote(new_remote_name, remote.url, remote.verify_ssl,
+                            remote.disabled)
+        self._remotes = OrderedDict([
+            (new_remote_name, new_remote) if k == remote_name else (k, v)
+            for k, v in self._remotes.items()
+        ])
+
+    def set_disabled_state(self, remote_name, state):
+        filtered_remotes = []
+        for remote in self._remotes.values():
+            if fnmatch.fnmatch(remote.name, remote_name):
+                if remote.disabled != state:
+                    filtered_remotes.append(remote.name)
+        for r in filtered_remotes:
+            remote = self._remotes[r]
+            self._remotes[r] = Remote(remote.name, remote.url,
+                                      remote.verify_ssl, state)
 
     def get_remote(self, remote_name):
         # Returns the remote defined by the name, or the default if is None
@@ -181,11 +214,17 @@ def get(self, remote_name):
 
     def __getitem__(self, remote_name):
         try:
-            return self._remotes[remote_name]
+            remote = self._remotes[remote_name]
+            if remote.disabled:
+                raise ConanException("Remote '%s' is disabled" % (remote_name))
+            else:
+                return remote
         except KeyError:
             raise NoRemoteAvailable("No remote '%s' defined in remotes" % (remote_name))
 
     def __delitem__(self, remote_name):
+        if remote_name in self._remotes and self._remotes[remote_name].disabled:
+            raise ConanException("Remote '%s' is disabled" % (remote_name))
         try:
             del self._remotes[remote_name]
         except KeyError:
@@ -193,7 +232,7 @@ def __delitem__(self, remote_name):
 
     def _upsert(self, remote_name, url, verify_ssl, insert):
         # Remove duplicates
-        updated_remote = Remote(remote_name, url, verify_ssl)
+        updated_remote = Remote(remote_name, url, verify_ssl, False)
         self._remotes.pop(remote_name, None)
         remotes_list = []
         renamed = None
@@ -227,13 +266,16 @@ def add(self, remote_name, url, verify_ssl=True, insert=None, force=None):
     def update(self, remote_name, url, verify_ssl=True, insert=None):
         if remote_name not in self._remotes:
             raise ConanException("Remote '%s' not found in remotes" % remote_name)
+        elif self._remotes[remote_name].disabled:
+            raise ConanException("Remote '%s' is disabled" % remote_name)
         self._add_update(remote_name, url, verify_ssl, insert)
 
     def _add_update(self, remote_name, url, verify_ssl, insert=None):
         prev_remote = self._get_by_url(url)
         if prev_remote and verify_ssl == prev_remote.verify_ssl and insert is None:
             raise ConanException("Remote '%s' already exists with same URL" % prev_remote.name)
-        updated_remote = Remote(remote_name, url, verify_ssl)
+        disabled = True if prev_remote and prev_remote.disabled else False
+        updated_remote = Remote(remote_name, url, verify_ssl, disabled)
         if insert is not None:
             try:
                 insert_index = int(insert)
@@ -351,6 +393,11 @@ def rename(self, remote_name, new_remote_name):
 
             remotes.save(self._filename)
 
+    def set_disabled_state(self, remote_name, state):
+        remotes = self.load_remotes()
+        remotes.set_disabled_state(remote_name, state)
+        remotes.save(self._filename)
+
     @property
     def refs_list(self):
         result = {}
diff --git a/conans/client/cmd/search.py b/conans/client/cmd/search.py
index ac8d0e476cf..5fe2ca061e5 100644
--- a/conans/client/cmd/search.py
+++ b/conans/client/cmd/search.py
@@ -24,9 +24,10 @@ def search_recipes(self, pattern, remote_name=None, case_sensitive=False):
             # Deprecate: 2.0 can remove this check
             if 'all' not in self._remotes:
                 for remote in self._remotes.values():
-                    refs = self._remote_manager.search_recipes(remote, pattern, ignorecase)
-                    if refs:
-                        references[remote.name] = refs
+                    if not remote.disabled:
+                        refs = self._remote_manager.search_recipes(remote, pattern, ignorecase)
+                        if refs:
+                            references[remote.name] = refs
                 return references
         # single remote
         remote = self._remotes[remote_name]
diff --git a/conans/client/command.py b/conans/client/command.py
index 0892380a71d..f8be9247d73 100644
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -1449,6 +1449,11 @@ def remote(self, *args):
         subparsers.add_parser('clean', help="Clean the list of remotes and all "
                                             "recipe-remote associations")
 
+        parser_enable = subparsers.add_parser('enable', help='Enable a remote')
+        parser_enable.add_argument('remote', help='Name of the remote')
+        parser_disable = subparsers.add_parser('disable', help='Disable a remote')
+        parser_disable.add_argument('remote', help='Name of the remote')
+
         args = parser.parse_args(*args)
 
         reference = args.reference if hasattr(args, 'reference') else None
@@ -1491,6 +1496,10 @@ def remote(self, *args):
             return self._conan.remote_update_pref(package_reference, remote_name)
         elif args.subcommand == "clean":
             return self._conan.remote_clean()
+        elif args.subcommand == "enable":
+            return self._conan.remote_set_disabled_state(remote_name, False)
+        elif args.subcommand == "disable":
+            return self._conan.remote_set_disabled_state(remote_name, True)
 
     def profile(self, *args):
         """
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
index 87d123d43db..9f7ab42f4b0 100644
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -915,7 +915,7 @@ def upload(self, pattern, package=None, remote_name=None, all_packages=False, co
 
     @api_method
     def remote_list(self):
-        return list(self.app.cache.registry.load_remotes().values())
+        return list(self.app.cache.registry.load_remotes().all_values())
 
     @api_method
     def remote_add(self, remote_name, url, verify_ssl=True, insert=None, force=None):
@@ -925,6 +925,10 @@ def remote_add(self, remote_name, url, verify_ssl=True, insert=None, force=None)
     def remote_remove(self, remote_name):
         return self.app.cache.registry.remove(remote_name)
 
+    @api_method
+    def remote_set_disabled_state(self, remote_name, state):
+        return self.app.cache.registry.set_disabled_state(remote_name, state)
+
     @api_method
     def remote_update(self, remote_name, url, verify_ssl=True, insert=None):
         return self.app.cache.registry.update(remote_name, url, verify_ssl, insert)
diff --git a/conans/client/conan_command_output.py b/conans/client/conan_command_output.py
index 31458492255..f91d4b90a49 100644
--- a/conans/client/conan_command_output.py
+++ b/conans/client/conan_command_output.py
@@ -30,9 +30,15 @@ def profile_list(self, profiles):
     def remote_list(self, remotes, raw):
         for r in remotes:
             if raw:
-                self._output.info("%s %s %s" % (r.name, r.url, r.verify_ssl))
+                disabled_str = " True" if r.disabled else ""
+                self._output.info(
+                    "%s %s %s %s" %
+                    (r.name, r.url, r.verify_ssl, disabled_str))
             else:
-                self._output.info("%s: %s [Verify SSL: %s]" % (r.name, r.url, r.verify_ssl))
+                disabled_str = ", Disabled: True" if r.disabled else ""
+                self._output.info(
+                    "%s: %s [Verify SSL: %s%s]" %
+                    (r.name, r.url, r.verify_ssl, disabled_str))
 
     def remote_ref_list(self, refs):
         for reference, remote_name in refs.items():
diff --git a/conans/test/functional/command/config_install_test.py b/conans/test/functional/command/config_install_test.py
index 3897d74d402..4957440c8bf 100644
--- a/conans/test/functional/command/config_install_test.py
+++ b/conans/test/functional/command/config_install_test.py
@@ -145,9 +145,10 @@ def _check(self, params):
         settings_path = self.client.cache.settings_path
         self.assertEqual(load(settings_path).splitlines(), settings_yml.splitlines())
         remotes = self.client.cache.registry.load_remotes()
-        self.assertEqual(list(remotes.values()), [Remote("myrepo1", "https://myrepourl.net", False),
-                                                  Remote("my-repo-2", "https://myrepo2.com", True),
-                                                  ])
+        self.assertEqual(list(remotes.values()), [
+            Remote("myrepo1", "https://myrepourl.net", False, False),
+            Remote("my-repo-2", "https://myrepo2.com", True, False),
+        ])
         self.assertEqual(sorted(os.listdir(self.client.cache.profiles_path)),
                          sorted(["default", "linux", "windows"]))
         self.assertEqual(load(os.path.join(self.client.cache.profiles_path, "linux")).splitlines(),
diff --git a/conans/test/functional/command/install_test.py b/conans/test/functional/command/install_test.py
index 575d3408578..c442bf208a6 100644
--- a/conans/test/functional/command/install_test.py
+++ b/conans/test/functional/command/install_test.py
@@ -2,6 +2,7 @@
 import platform
 import textwrap
 import unittest
+from collections import OrderedDict
 
 from conans.client.tools.oss import detected_os
 from conans.model.info import ConanInfo
@@ -631,3 +632,33 @@ class MyPkg(ConanFile):
         # Try this syntax to upload too
         client.run('install lib/1.0@')
         client.run('upload lib/1.0@ -c --all')
+
+    def install_disabled_remote_test(self):
+        client = TestClient(servers={"default": TestServer()},
+                            users={"default": [("lasote", "mypass")]})
+        client.save({"conanfile.py": str(TestConanFile("Pkg", "0.1"))})
+        client.run("create . lasote/testing")
+        client.run("upload * --confirm --all -r default")
+        client.run("remote disable default")
+        client.run("install Pkg/0.1@lasote/testing -r default", assert_error=True)
+        self.assertIn("ERROR: Remote 'default' is disabled", client.out)
+        client.run("remote enable default")
+        client.run("install Pkg/0.1@lasote/testing -r default")
+        client.run("remote disable default")
+        client.run("install Pkg/0.1@lasote/testing --update", assert_error=True)
+        self.assertIn("ERROR: Remote 'default' is disabled", client.out)
+
+    def install_skip_disabled_remote_test(self):
+        client = TestClient(servers=OrderedDict({"default": TestServer(),
+                                                 "server2": TestServer(),
+                                                 "server3": TestServer()}),
+                            users={"default": [("lasote", "mypass")],
+                                   "server3": [("lasote", "mypass")]})
+        client.save({"conanfile.py": str(TestConanFile("Pkg", "0.1"))})
+        client.run("create . lasote/testing")
+        client.run("upload * --confirm --all -r default")
+        client.run("upload * --confirm --all -r server3")
+        client.run("remove * -f")
+        client.run("remote disable default")
+        client.run("install Pkg/0.1@lasote/testing", assert_error=False)
+        self.assertNotIn("Trying with 'default'...", client.out)
diff --git a/conans/test/functional/command/remote_test.py b/conans/test/functional/command/remote_test.py
index cc8feacc4af..7b0dd066192 100644
--- a/conans/test/functional/command/remote_test.py
+++ b/conans/test/functional/command/remote_test.py
@@ -293,6 +293,57 @@ def verify_ssl_test(self):
         self.assertEqual(data["remotes"][3]["url"], "http://someurl4")
         self.assertEqual(data["remotes"][3]["verify_ssl"], False)
 
+    def remote_disable_test(self):
+        client = TestClient()
+        client.run("remote add my-remote0 http://someurl0")
+        client.run("remote add my-remote1 http://someurl1")
+        client.run("remote add my-remote2 http://someurl2")
+        client.run("remote add my-remote3 http://someurl3")
+        client.run("remote disable my-remote0")
+        client.run("remote disable my-remote3")
+        registry = load(client.cache.registry_path)
+        data = json.loads(registry)
+        self.assertEqual(data["remotes"][0]["name"], "my-remote0")
+        self.assertEqual(data["remotes"][0]["url"], "http://someurl0")
+        self.assertEqual(data["remotes"][0]["disabled"], True)
+        self.assertEqual(data["remotes"][3]["name"], "my-remote3")
+        self.assertEqual(data["remotes"][3]["url"], "http://someurl3")
+        self.assertEqual(data["remotes"][3]["disabled"], True)
+
+        client.run("remote disable *")
+        registry = load(client.cache.registry_path)
+        data = json.loads(registry)
+        for remote in data["remotes"]:
+            self.assertEqual(remote["disabled"], True)
+
+        client.run("remote enable *")
+        registry = load(client.cache.registry_path)
+        data = json.loads(registry)
+        for remote in data["remotes"]:
+            with self.assertRaises(KeyError):
+                disabled = remote["disabled"]
+
+    def remove_disabled_remote_test(self):
+        client = TestClient()
+        client.run("remote add my-remote3 http://someurl0")
+        client.run("remote disable my-remote3")
+        client.run("remote remove my-remote3", assert_error=True)
+        self.assertIn("ERROR: Remote 'my-remote3' is disabled", client.out)
+
+    def rename_disabled_remote_test(self):
+        client = TestClient()
+        client.run("remote add my-remote3 http://someurl0")
+        client.run("remote disable my-remote3")
+        client.run("remote rename my-remote3 new_name", assert_error=True)
+        self.assertIn("ERROR: Remote 'my-remote3' is disabled", client.out)
+
+    def update_disabled_remote_test(self):
+        client = TestClient()
+        client.run("remote add my-remote3 http://someurl0")
+        client.run("remote disable my-remote3")
+        client.run("remote update my-remote3 http://someurl1 True", assert_error=True)
+        self.assertIn("ERROR: Remote 'my-remote3' is disabled", client.out)
+
     def verify_ssl_error_test(self):
         client = TestClient()
         client.run("remote add my-remote http://someurl some_invalid_option=foo", assert_error=True)
diff --git a/conans/test/functional/command/search_test.py b/conans/test/functional/command/search_test.py
index 6e90816db07..65aeeec9fc3 100644
--- a/conans/test/functional/command/search_test.py
+++ b/conans/test/functional/command/search_test.py
@@ -247,6 +247,20 @@ class Test(ConanFile):
                         lib/1.0@foo/bar
                         lib/1.0@user/channel"""), client.out)
 
+    def search_disabled_remote_test(self):
+        self.client.run("remote disable search_able")
+        self.client.run("search * -r search_able", assert_error=True)
+        self.assertIn("ERROR: Remote 'search_able' is disabled", self.client.out)
+
+    def search_skip_disabled_remote_test(self):
+        os.rmdir(self.servers["local"].server_store.store)
+        self._copy_to_server(self.client.cache, self.servers["local"].server_store)
+        os.rmdir(self.servers["search_able"].server_store.store)
+        self._copy_to_server(self.client.cache, self.servers["search_able"].server_store)
+        self.client.run("remote disable local")
+        self.client.run("search Hello* -r all")
+        self.assertNotIn("Remote 'local':", self.client.out)
+
     def recipe_search_all_test(self):
         os.rmdir(self.servers["local"].server_store.store)
         self._copy_to_server(self.client.cache, self.servers["local"].server_store)
diff --git a/conans/test/functional/configuration/registry_test.py b/conans/test/functional/configuration/registry_test.py
index 392a395c6cf..93b21b74a84 100644
--- a/conans/test/functional/configuration/registry_test.py
+++ b/conans/test/functional/configuration/registry_test.py
@@ -27,7 +27,7 @@ def retro_compatibility_test(self):
         migrate_registry_file(cache, output)
         registry = RemoteRegistry(cache, output)
         self.assertEqual(list(registry.load_remotes().values()),
-                         [("conan.io", "https://server.conan.io", True)])
+                         [("conan.io", "https://server.conan.io", True, False)])
 
     def to_json_migration_test(self):
         cache_folder = temp_folder()
@@ -44,7 +44,7 @@ def to_json_migration_test(self):
         self.assertIn("conan.io: https://server.conan.io", client.out)
         registry = client.cache.registry
         self.assertEqual(list(registry.load_remotes().values()),
-                         [("conan.io", "https://server.conan.io", True)])
+                         [("conan.io", "https://server.conan.io", True, False)])
         ref1 = ConanFileReference.loads('lib/1.0@conan/stable')
         ref2 = ConanFileReference.loads('other/1.0@lasote/testing')
         expected = {ref1: 'conan.io', ref2: 'conan.io'}
@@ -65,36 +65,36 @@ def add_remove_update_test(self):
         # Add
         registry.add("local", "http://localhost:9300")
         self.assertEqual(list(registry.load_remotes().values()),
-                         [("conan-center", "https://conan.bintray.com", True),
-                          ("local", "http://localhost:9300", True)])
+                         [("conan-center", "https://conan.bintray.com", True, False),
+                          ("local", "http://localhost:9300", True, False)])
         # Add
         registry.add("new", "new_url", False)
         self.assertEqual(list(registry.load_remotes().values()),
-                         [("conan-center", "https://conan.bintray.com", True),
-                          ("local", "http://localhost:9300", True),
-                          ("new", "new_url", False)])
+                         [("conan-center", "https://conan.bintray.com", True, False),
+                          ("local", "http://localhost:9300", True, False),
+                          ("new", "new_url", False, False)])
         with self.assertRaises(ConanException):
             registry.add("new", "new_url")
         # Update
         registry.update("new", "other_url")
         self.assertEqual(list(registry.load_remotes().values()),
-                         [("conan-center", "https://conan.bintray.com", True),
-                          ("local", "http://localhost:9300", True),
-                          ("new", "other_url", True)])
+                         [("conan-center", "https://conan.bintray.com", True, False),
+                          ("local", "http://localhost:9300", True, False),
+                          ("new", "other_url", True, False)])
         with self.assertRaises(ConanException):
             registry.update("new2", "new_url")
 
         registry.update("new", "other_url", False)
         self.assertEqual(list(registry.load_remotes().values()),
-                         [("conan-center", "https://conan.bintray.com", True),
-                          ("local", "http://localhost:9300", True),
-                          ("new", "other_url", False)])
+                         [("conan-center", "https://conan.bintray.com", True, False),
+                          ("local", "http://localhost:9300", True, False),
+                          ("new", "other_url", False, False)])
 
         # Remove
         registry.remove("local")
         self.assertEqual(list(registry.load_remotes().values()),
-                         [("conan-center", "https://conan.bintray.com", True),
-                          ("new", "other_url", False)])
+                         [("conan-center", "https://conan.bintray.com", True, False),
+                          ("new", "other_url", False, False)])
         with self.assertRaises(ConanException):
             registry.remove("new2")
 
@@ -116,17 +116,17 @@ def insert_test(self):
         cache = ClientCache(tmp_folder, output)
         registry = RemoteRegistry(cache, output)
         registry.add("repo1", "url1", True, insert=0)
-        self.assertEqual(list(registry.load_remotes().values()), [Remote("repo1", "url1", True),
-                         Remote("conan.io", "https://server.conan.io", True)])
+        self.assertEqual(list(registry.load_remotes().values()), [Remote("repo1", "url1", True, False),
+                         Remote("conan.io", "https://server.conan.io", True, False)])
         registry.add("repo2", "url2", True, insert=1)
-        self.assertEqual(list(registry.load_remotes().values()), [Remote("repo1", "url1", True),
-                         Remote("repo2", "url2", True),
-                         Remote("conan.io", "https://server.conan.io", True)])
+        self.assertEqual(list(registry.load_remotes().values()), [Remote("repo1", "url1", True, False),
+                         Remote("repo2", "url2", True, False),
+                         Remote("conan.io", "https://server.conan.io", True, False)])
         registry.add("repo3", "url3", True, insert=5)
-        self.assertEqual(list(registry.load_remotes().values()), [Remote("repo1", "url1", True),
-                         Remote("repo2", "url2", True),
-                         Remote("conan.io", "https://server.conan.io", True),
-                         Remote("repo3", "url3", True)])
+        self.assertEqual(list(registry.load_remotes().values()), [Remote("repo1", "url1", True, False),
+                         Remote("repo2", "url2", True, False),
+                         Remote("conan.io", "https://server.conan.io", True, False),
+                         Remote("repo3", "url3", True, False)])
 
     def test_remote_none(self):
         """ RemoteRegistry should be able to deal when the URL is None
@@ -138,12 +138,42 @@ def test_remote_none(self):
 
         registry.add("foobar", None)
         self.assertEqual(list(registry.load_remotes().values()),
-                         [("conan-center", "https://conan.bintray.com", True),
-                          ("foobar", None, True)])
+                         [("conan-center", "https://conan.bintray.com", True, False),
+                          ("foobar", None, True, False)])
         self.assertIn("WARN: The URL is empty. It must contain scheme and hostname.", cache._output)
         registry.remove("foobar")
 
         registry.update("conan-center", None)
         self.assertEqual(list(registry.load_remotes().values()),
-                         [("conan-center", None, True)])
+                         [("conan-center", None, True, False)])
         self.assertIn("WARN: The URL is empty. It must contain scheme and hostname.", cache._output)
+
+    def enable_disable_remotes_test(self):
+        f = os.path.join(temp_folder(), "aux_file")
+        Remotes().save(f)
+        cache = ClientCache(os.path.dirname(f), TestBufferConanOutput())
+        registry = cache.registry
+
+        registry.add("local", "http://localhost:9300")
+        registry.set_disabled_state("local", True)
+        self.assertEqual(list(registry.load_remotes().all_values()),
+                         [("conan-center", "https://conan.bintray.com", True, False),
+                          ("local", "http://localhost:9300", True, True)])
+
+        self.assertEqual(list(registry.load_remotes().values()),
+                         [("conan-center", "https://conan.bintray.com", True, False)])
+
+        registry.set_disabled_state("conan-center", True)
+        self.assertEqual(list(registry.load_remotes().all_values()),
+                         [("conan-center", "https://conan.bintray.com", True, True),
+                          ("local", "http://localhost:9300", True, True)])
+
+        self.assertEqual(list(registry.load_remotes().values()), [])
+
+        registry.set_disabled_state("*", False)
+        self.assertEqual(list(registry.load_remotes().values()),
+                         [("conan-center", "https://conan.bintray.com", True, False),
+                          ("local", "http://localhost:9300", True, False)])
+
+        registry.set_disabled_state("*", True)
+        self.assertEqual(list(registry.load_remotes().values()), [])


Code review comments:
diff_hunk: @@ -24,9 +24,10 @@ def search_recipes(self, pattern, remote_name=None, case_sensitive=False):
             # Deprecate: 2.0 can remove this check
             if 'all' not in self._remotes:
                 for remote in self._remotes.values():
-                    refs = self._remote_manager.search_recipes(remote, pattern, ignorecase)
-                    if refs:
-                        references[remote.name] = refs
+                    if not remote.disabled:
path: conans/client/cmd/search.py
text: @user1:
needed the if?