Repository: conan-io/conan

Issue:
[bug] conan remove does not find recipe
Hi there,
I tried to delete every version of a recipe in a certain user/channel but Conan stopped working with the error that it could not find the recipe:
```
conan remove cygwin_installer/*@user/channel
ERROR: Recipe not found: 'cygwin_installer/*@user/channel'
```
But when I modify the recipe name and append an asterix it works:
```
conan remove cygwin_installer*/*@user/channel
Are you sure you want to delete from 'cygwin_installer/2.9.0@user/channel' (yes/no):
```

Is this intentional?


Discussion:

Resolving patch:
diff --git a/conans/client/remover.py b/conans/client/remover.py
--- a/conans/client/remover.py
+++ b/conans/client/remover.py
@@ -3,7 +3,7 @@
 from conans.client.cache.remote_registry import Remote
 from conans.errors import ConanException, PackageNotFoundException, RecipeNotFoundException
 from conans.errors import NotFoundException
-from conans.model.ref import ConanFileReference, PackageReference
+from conans.model.ref import ConanFileReference, PackageReference, check_valid_ref
 from conans.paths import SYSTEM_REQS, rm_conandir
 from conans.search.search import filter_outdated, search_packages, search_recipes
 from conans.util.log import logger
@@ -168,7 +168,7 @@ def remove(self, pattern, remote_name, src=None, build_ids=None, package_ids_fil
             else:
                 refs = self._remote_manager.search_recipes(remote, pattern)
         else:
-            if input_ref:
+            if input_ref and check_valid_ref(input_ref, allow_pattern=False):
                 refs = []
                 if self._cache.installed_as_editable(input_ref):
                     raise ConanException(self._message_removing_editable(input_ref))


Test patch:
diff --git a/conans/test/functional/command/remove_test.py b/conans/test/functional/command/remove_test.py
--- a/conans/test/functional/command/remove_test.py
+++ b/conans/test/functional/command/remove_test.py
@@ -284,6 +284,42 @@ def basic_packages_test(self):
                              os.listdir(os.path.join(self.client.storage_folder,
                                                      "Hello/2.4.11/myuser/testing")))
 
+    def _validate_remove_all_hello_packages(self):
+        self.assert_folders(local_folders={"H1": None, "H2": None, "B": [1, 2], "O": [1, 2]},
+                            remote_folders={"H1": [1, 2], "H2": [1, 2], "B": [1, 2], "O": [1, 2]},
+                            build_folders={"H1": None, "H2": None, "B": [1, 2], "O": [1, 2]},
+                            src_folders={"H1": False, "H2": False, "B": True, "O": True})
+        folders = os.listdir(self.client.storage_folder)
+        six.assertCountEqual(self, ["Other", "Bye"], folders)
+
+    def test_remove_any_package_version(self):
+        self.client.run("remove Hello/*@myuser/testing -f")
+        self._validate_remove_all_hello_packages()
+
+    def test_remove_any_package_version_channel(self):
+        self.client.run("remove Hello/*@*/testing -f")
+        self._validate_remove_all_hello_packages()
+
+    def test_remove_any_package_version_channel(self):
+        self.client.run("remove Hello/*@*/* -f")
+        self._validate_remove_all_hello_packages()
+
+    def _validate_remove_hello_1_4_10(self):
+        self.assert_folders(local_folders={"H1": None, "H2": [1, 2], "B": [1, 2], "O": [1, 2]},
+                            remote_folders={"H1": [1, 2], "H2": [1, 2], "B": [1, 2], "O": [1, 2]},
+                            build_folders={"H1": None, "H2": [1, 2], "B": [1, 2], "O": [1, 2]},
+                            src_folders={"H1": False, "H2": True, "B": True, "O": True})
+        folders = os.listdir(self.client.storage_folder)
+        six.assertCountEqual(self, ["Hello", "Other", "Bye"], folders)
+
+    def test_remove_any_package_channel(self):
+        self.client.run("remove Hello/1.4.10@*/testing -f")
+        self._validate_remove_hello_1_4_10()
+
+    def test_remove_any_package_channel(self):
+        self.client.run("remove Hello/1.4.10@myuser/* -f")
+        self._validate_remove_hello_1_4_10()
+
     def builds_test(self):
         mocked_user_io = UserIO(out=TestBufferConanOutput())
         mocked_user_io.request_boolean = Mock(return_value=True)