Repository: meltano/meltano

Issue:
Meltano remove lock file bug
From #5977 I was removing an installed plugin and got this on the command line:

```
❯ meltano remove orchestrator airflow
2022-06-02T15:20:50.385299Z [info     ] Environment 'dev' is active

Removing orchestrator 'airflow'...

Reset orchestrator 'airflow' plugin settings in the system database
Removed orchestrator 'airflow' from meltano.yml
Removed orchestrator 'airflow' from .meltano/orchestrators
Could not find orchestrator 'airflow' in /Users/taylormurphy/Documents/Projects/dev/meltano/addfromhub/plugins/orchestrators/airflow--original.lock to remove
```

In my `plugins/` folder I still have:

```
plugins
  files/
     airflow--meltano.lock
  orchestrators/
     airflow--apache.lock
```

@edgarrmondragon cc @aaronsteers 


Discussion:
@tayloramurphy Ah, the quirks of putting the variant in the filename. `meltano remove` doesn't take the variant name into account and thus tries to remove a plugin with the fallback `original` variant.

This wasn't scoped in the original lockfile issue so I didn't dive too deep, but I think I have a fix that relies on the plugin still being present in `meltano.yml`, otherwise we might need to look for lockfiles of any variant (e.g. `orchestrators/airflow*.lock`) and remove those.

Resolving patch:
diff --git a/src/meltano/core/plugin_location_remove.py b/src/meltano/core/plugin_location_remove.py
--- a/src/meltano/core/plugin_location_remove.py
+++ b/src/meltano/core/plugin_location_remove.py
@@ -144,21 +144,28 @@ def __init__(self, plugin, project: Project):
             plugin: The plugin to remove.
             project: The Meltano project.
         """
-        path = project.plugin_lock_path(plugin.type, plugin.name, plugin.variant)
-        super().__init__(plugin, str(path))
-        self.path = path
+        lockfile_dir = project.root_plugins_dir(plugin.type)
+        glob_expr = f"{plugin.name}*.lock"
+        super().__init__(
+            plugin,
+            str(lockfile_dir.relative_to(project.root).joinpath(glob_expr)),
+        )
+
+        self.paths = list(lockfile_dir.glob(glob_expr))
 
     def remove(self):
         """Remove the plugin from `plugins/`."""
-        try:
-            self.path.unlink()
-        except FileNotFoundError:
+        if not self.paths:
             self.remove_status = PluginLocationRemoveStatus.NOT_FOUND
             return
-        except OSError as err:
-            self.remove_status = PluginLocationRemoveStatus.ERROR
-            self.message = err.strerror
-            return
+
+        for path in self.paths:
+            try:
+                path.unlink()
+            except OSError as err:
+                self.remove_status = PluginLocationRemoveStatus.ERROR
+                self.message = err.strerror
+                return
 
         self.remove_status = PluginLocationRemoveStatus.REMOVED
 


Test patch:
diff --git a/tests/meltano/core/test_plugin_remove_service.py b/tests/meltano/core/test_plugin_remove_service.py
--- a/tests/meltano/core/test_plugin_remove_service.py
+++ b/tests/meltano/core/test_plugin_remove_service.py
@@ -89,6 +89,14 @@ def test_remove(
                 subject.project.meltano_dir().joinpath(plugin.type, plugin.name)
             )
 
+            # check removed lock files
+            lock_file_paths = list(
+                subject.project.root_plugins_dir(plugin.type).glob(
+                    f"{plugin.name}*.lock"
+                )
+            )
+            assert all(not path.exists() for path in lock_file_paths)
+
     def test_remove_not_added_or_installed(self, subject: PluginRemoveService):
         plugins = list(subject.plugins_service.plugins())
         removed_plugins, total_plugins = subject.remove_plugins(plugins)