Repository: OctoPrint/OctoPrint

Issue:
[Request] Plugin list dump
**Is your feature request related to a problem? Please describe.**
Sometimes it is convenient to have a complete list of plugins and versions when not in the plugin manager (or, indeed even in OP) - especially useful for sending to plugin authors to look for conflicts.  While the plugin manager has the scrollable list, it's not in a form that can be used in this manner, as it cannot be copied.

**Describe the solution you'd like**
Please add to the Plugin Manager the ability to generate a list of currently installed plugins, with status, version, author/homepage link.  This could be either generated as a .csv or text file (for downloading), or as an .html web page opened in a new browser tab, which the user could view and/or print

**Describe alternatives you've considered**
none, really

**Additional context**
Add any other context or screenshots about the feature request here.



Discussion:
```
http://<ip>/api/plugin/pluginmanager
```
Will return a list of all available plugins in the repo and all those installed on the machine and their status and additional details. The plugins key has all the installed plugins data and the repository key has all the available plugins and their data.

Great, glad it's already there somehow.  Took me a second to figure out where to put that URL :) 

Thank you for all your work.
Closing my own issue because the solution (in a way) already exists.
This info also comes through in the `octoprint.log` so if you are submitting an issue, then the information can be read from there.

I'm tempted to grab this and make feature, just a 'export list' button in the plugin manager. I know I could have used it when working out a bug conflict, to export them and then cross off one by one in a test.

Re-opened for now, since I think there may be more to this feature than already exists and would like to see maybe a little more discussion on it.
> I'm tempted to grab this and make feature, just a 'export list' button in the plugin manager. I know I could have used it when working out a bug conflict, to export them and then cross off one by one in a test.

Exactly what I was asking for, I only closed it because there was a workaround, and didn't want to cause extra workload on the programmers
.
For me, workaround are not good enough 🙂 

When I first made my plugins, I was eating feature requests for breakfast. Its slowed down now, but I still (mostly) enjoy it.

Marking this as 'grabbed' since I'll take a look at doing it. If I can't, I'll report back here.
There was a similar request on the community forum by Albert in regard to providing a separate endpoint that provides just the installed plugin's part and not the whole repo.
Thinking about this a little more, and wanted to get some opinions. What do you think of an import/export function, where you can export a list of plugins (covering this current FR), which could then be uploaded to a different instance of OctoPrint and would install all the plugins in the list.

Unfortunately that's beyond me I think, so maybe @foosel if you think this is a good idea it could go down as a feature request for that?
> Thinking about this a little more, and wanted to get some opinions. What do you think of an import/export function, where you can export a list of plugins (covering this current FR), which could then be uploaded to a different instance of OctoPrint and would install all the plugins in the list.

This is basically what I do when I set up a new instance, by doing a backup of one, then doing a restore from that  backup to the instance that I want to clone, so that all plugins and settings are identical across identical machines.  I then simply change the things such as machine name, etc., that set them apart.

If you do this, it would be more convenient than doing a full install and then a restore as I do now.  You would need to include the option to either clone the settings, too, or do a fresh install of the plugins (or even a selectable list as to which option [clone , new or ignore] of install for each plugin).
We could move the current plugin list backup functionality from the backup plugin into the pluginmanager and expose it on the UI. The backup plugin could then be adjusted to utilize it during backup/restore.

Regarding the setup of a new instance, that sounds like we'd need an additional "copy from backup" maybe, which would copy over all the settings but *not* the password salt and cookie hashs as it usually does. I'd consider that a different feature request though.
Adjusting the backup & restore plugin seems like a good idea, since the functionality already there. Reflecting on this, I've got a little bit too much to do at the moment to look into it and properly... so ungrabbing. 🙁 

Resolving patch:
diff --git a/src/octoprint/plugins/backup/__init__.py b/src/octoprint/plugins/backup/__init__.py
--- a/src/octoprint/plugins/backup/__init__.py
+++ b/src/octoprint/plugins/backup/__init__.py
@@ -8,7 +8,6 @@
 from octoprint.access import ADMIN_GROUP
 from octoprint.access.permissions import Permissions
 from octoprint.events import Events
-from octoprint.plugin.core import FolderOrigin
 from octoprint.server import NO_CONTENT
 from octoprint.server.util.flask import no_firstrun_access
 from octoprint.settings import default_settings
@@ -980,22 +979,16 @@ def add_to_zip(source, target, ignored=None):
                 )
 
                 # add list of installed plugins
-                plugins = []
-                plugin_folder = settings.global_get_basefolder("plugins")
-                for plugin in plugin_manager.plugins.values():
-                    if plugin.bundled or (
-                        isinstance(plugin.origin, FolderOrigin)
-                        and plugin.origin.folder == plugin_folder
-                    ):
-                        # ignore anything bundled or from the plugins folder we already include in the backup
-                        continue
-
-                    plugins.append(
-                        {"key": plugin.key, "name": plugin.name, "url": plugin.url}
+                helpers = plugin_manager.get_helpers(
+                    "pluginmanager", "generate_plugins_json"
+                )
+                if helpers and "generate_plugins_json" in helpers:
+                    plugins = helpers["generate_plugins_json"](
+                        settings=settings, plugin_manager=plugin_manager
                     )
 
-                if len(plugins):
-                    zip.writestr("plugin_list.json", json.dumps(plugins))
+                    if len(plugins):
+                        zip.writestr("plugin_list.json", json.dumps(plugins))
 
             shutil.move(temporary_path, final_path)
 
diff --git a/src/octoprint/plugins/pluginmanager/__init__.py b/src/octoprint/plugins/pluginmanager/__init__.py
--- a/src/octoprint/plugins/pluginmanager/__init__.py
+++ b/src/octoprint/plugins/pluginmanager/__init__.py
@@ -401,6 +401,22 @@ def upload_file(self):
                     )
                 )
 
+    @octoprint.plugin.BlueprintPlugin.route("/export", methods=["GET"])
+    @no_firstrun_access
+    @Permissions.PLUGIN_PLUGINMANAGER_MANAGE.require(403)
+    def export_plugin_list(self):
+        import json
+
+        import flask
+
+        plugins = self.generate_plugins_json(self._settings, self._plugin_manager)
+
+        return flask.Response(
+            json.dumps(plugins),
+            mimetype="text/plain",
+            headers={"Content-Disposition": 'attachment; filename="plugins.json"'},
+        )
+
     def is_blueprint_protected(self):
         return False
 
@@ -1755,6 +1771,24 @@ def _get_plugins(self):
 
         return result
 
+    @staticmethod
+    def generate_plugins_json(
+        settings, plugin_manager, ignore_bundled=True, ignore_plugins_folder=True
+    ):
+        plugins = []
+        plugin_folder = settings.getBaseFolder("plugins")
+        for plugin in plugin_manager.plugins.values():
+            if (ignore_bundled and plugin.bundled) or (
+                ignore_plugins_folder
+                and isinstance(plugin.origin, octoprint.plugin.core.FolderOrigin)
+                and plugin.origin.folder == plugin_folder
+            ):
+                # ignore bundled or from the plugins folder already included in the backup
+                continue
+
+            plugins.append({"key": plugin.key, "name": plugin.name, "url": plugin.url})
+        return plugins
+
     def _to_external_plugin(self, plugin):
         return {
             "key": plugin.key,
@@ -1898,3 +1932,8 @@ def __plugin_load__():
         "octoprint.events.register_custom_events": _register_custom_events,
         "octoprint.access.permissions": __plugin_implementation__.get_additional_permissions,
     }
+
+    global __plugin_helpers__
+    __plugin_helpers__ = {
+        "generate_plugins_json": __plugin_implementation__.generate_plugins_json,
+    }


Test patch: