From 4e220b525411de6becff7bcabe81640143154b83 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 28 Jul 2022 14:30:55 +0200 Subject: [PATCH] Implement more plugin list commands (#2094) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement commands to list available plugins and installed plugins separately. Co-authored-by: René Pfeuffer --- gradle/changelog/cli_plugin_management.yaml | 2 +- .../cli/PluginListAvailableCommand.java | 58 ++++++++++++ .../scm/plugin/cli/PluginListCommand.java | 6 +- .../cli/PluginListInstalledCommand.java | 58 ++++++++++++ .../cli/PluginSingleListBaseCommand.java | 87 ++++++++++++++++++ .../resources/sonia/scm/cli/i18n.properties | 2 + .../sonia/scm/cli/i18n_de.properties | 3 + .../cli/PluginListAvailableCommandTest.java | 89 +++++++++++++++++++ .../cli/PluginListInstalledCommandTest.java | 84 +++++++++++++++++ 9 files changed, 385 insertions(+), 4 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListAvailableCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListInstalledCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginSingleListBaseCommand.java create mode 100644 scm-webapp/src/test/java/sonia/scm/plugin/cli/PluginListAvailableCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/plugin/cli/PluginListInstalledCommandTest.java diff --git a/gradle/changelog/cli_plugin_management.yaml b/gradle/changelog/cli_plugin_management.yaml index a07b3aa59b..b7dede8a86 100644 --- a/gradle/changelog/cli_plugin_management.yaml +++ b/gradle/changelog/cli_plugin_management.yaml @@ -1,2 +1,2 @@ - type: added - description: Enable plugin management via CLI ([#2087](https://github.com/scm-manager/scm-manager/pull/2087)) + description: Enable plugin management via CLI ([#2087](https://github.com/scm-manager/scm-manager/pull/2087)) & ([#2094](https://github.com/scm-manager/scm-manager/pull/2094)) diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListAvailableCommand.java b/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListAvailableCommand.java new file mode 100644 index 0000000000..2d18bd3837 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListAvailableCommand.java @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.TemplateRenderer; +import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginManager; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.stream.Collectors; + +@ParentCommand(value = PluginCommand.class) +@CommandLine.Command(name = "list-available", aliases = "lsa") +class PluginListAvailableCommand extends PluginSingleListBaseCommand implements Runnable { + + private final PluginManager manager; + + @Inject + public PluginListAvailableCommand(TemplateRenderer templateRenderer, PluginManager manager) { + super(templateRenderer, manager); + this.manager = manager; + } + + @Override + public void run() { + Collection plugins = manager.getAvailable().stream() + .map(p -> p.getDescriptor().getInformation()) + .sorted((a, b) -> a.getName().compareToIgnoreCase(b.getName())) + .collect(Collectors.toList()); + String[] header = {"scm.plugin.name", "scm.plugin.displayName", "scm.plugin.availableVersion", "scm.plugin.pending"}; + renderResult(plugins, header); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListCommand.java b/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListCommand.java index b85ea088dd..e3622029f7 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListCommand.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListCommand.java @@ -24,7 +24,7 @@ package sonia.scm.plugin.cli; -import com.cronutils.utils.VisibleForTesting; +import com.google.common.annotations.VisibleForTesting; import lombok.Getter; import lombok.Setter; import picocli.CommandLine; @@ -59,13 +59,13 @@ class PluginListCommand implements Runnable { @CommandLine.Option(names = {"--short", "-s"}) private boolean useShortTemplate; - private static final String TABLE_TEMPLATE = String.join("\n", + static final String TABLE_TEMPLATE = String.join("\n", "{{#rows}}", "{{#cols}}{{#row.first}}{{#upper}}{{value}}{{/upper}}{{/row.first}}{{^row.first}}{{value}}{{/row.first}}{{^last}} {{/last}}{{/cols}}", "{{/rows}}" ); - private static final String SHORT_TEMPLATE = String.join("\n", + static final String SHORT_TEMPLATE = String.join("\n", "{{#plugins}}", "{{name}}", "{{/plugins}}" diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListInstalledCommand.java b/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListInstalledCommand.java new file mode 100644 index 0000000000..f0882b763a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginListInstalledCommand.java @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.TemplateRenderer; +import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginManager; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.stream.Collectors; + +@ParentCommand(value = PluginCommand.class) +@CommandLine.Command(name = "list-installed", aliases = "lsi") +public class PluginListInstalledCommand extends PluginSingleListBaseCommand implements Runnable { + + private final PluginManager manager; + + @Inject + PluginListInstalledCommand(TemplateRenderer templateRenderer, PluginManager manager) { + super(templateRenderer, manager); + this.manager = manager; + } + + @Override + public void run() { + Collection plugins = manager.getInstalled().stream() + .map(p -> p.getDescriptor().getInformation()) + .sorted((a, b) -> a.getName().compareToIgnoreCase(b.getName())) + .collect(Collectors.toList()); + String[] header = {"scm.plugin.name", "scm.plugin.displayName", "scm.plugin.installedVersion", "scm.plugin.pending"}; + renderResult(plugins, header); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginSingleListBaseCommand.java b/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginSingleListBaseCommand.java new file mode 100644 index 0000000000..5e8afb4b72 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/cli/PluginSingleListBaseCommand.java @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.Table; +import sonia.scm.cli.TemplateRenderer; +import sonia.scm.plugin.PendingPlugins; +import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginManager; + +import java.util.Collection; +import java.util.Map; + +import static sonia.scm.plugin.cli.PluginListCommand.SHORT_TEMPLATE; +import static sonia.scm.plugin.cli.PluginListCommand.TABLE_TEMPLATE; + +abstract class PluginSingleListBaseCommand { + + @CommandLine.Mixin + private final TemplateRenderer templateRenderer; + private final PluginManager manager; + + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + @CommandLine.Option(names = {"--short", "-s"}, descriptionKey = "scm.plugin.list.short") + private boolean useShortTemplate; + + PluginSingleListBaseCommand(TemplateRenderer templateRenderer, PluginManager manager) { + this.templateRenderer = templateRenderer; + this.manager = manager; + } + + void renderResult(Collection plugins, String[] header) { + if (useShortTemplate) { + templateRenderer.renderToStdout(SHORT_TEMPLATE, Map.of("plugins", plugins)); + } else { + Table table = templateRenderer.createTable(); + String yes = spec.resourceBundle().getString("yes"); + table.addHeader(header); + + PendingPlugins pendingPlugins = manager.getPending(); + for (PluginInformation plugin : plugins) { + table.addRow( + plugin.getName(), + plugin.getDisplayName(), + plugin.getVersion(), + pendingPlugins.isPending(plugin.getName()) ? yes : "" + ); + } + templateRenderer.renderToStdout(TABLE_TEMPLATE, Map.of("rows", table, "plugins", plugins)); + } + } + + @VisibleForTesting + void setUseShortTemplate(boolean useShortTemplate) { + this.useShortTemplate = useShortTemplate; + } + + @VisibleForTesting + void setSpec(CommandLine.Model.CommandSpec spec) { + this.spec = spec; + } +} diff --git a/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties b/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties index 14f3705dc4..283a11868b 100644 --- a/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties +++ b/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties @@ -322,6 +322,8 @@ scm.plugin.availableVersion = Available scm.plugin.pending = Pending? scm.plugin.list.usage.description.0 = List all plugins with versions scm.plugin.list.short = Show only the plugin names +scm.plugin.list-available.usage.description.0 = List all available plugins with versions +scm.plugin.list-installed.usage.description.0 = List all installed plugins with versions ### Add plugin scm.plugin.add.usage.description.0 = Installs the plugin with the required dependencies diff --git a/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties b/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties index 2f05198b4c..4fbe25589c 100644 --- a/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties +++ b/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties @@ -322,6 +322,9 @@ scm.plugin.availableVersion = Verf scm.plugin.pending = Änderung ausstehend scm.plugin.list.usage.description.0 = Listet alle Plugins mit Versionen auf scm.plugin.list.short = Zeigt nur die Plugin Namen an +scm.plugin.list-available.usage.description.0 = Listet alle verfügbaren Plugins mit Versionen auf +scm.plugin.list-installed.usage.description.0 = Listet alle installierten Plugins mit Versionen auf + ### Add plugin scm.plugin.add.usage.description.0 = Installiert das Plugin inklusive Abhängigkeiten diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/cli/PluginListAvailableCommandTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/cli/PluginListAvailableCommandTest.java new file mode 100644 index 0000000000..202659228a --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/plugin/cli/PluginListAvailableCommandTest.java @@ -0,0 +1,89 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.TemplateTestRenderer; +import sonia.scm.plugin.PendingPlugins; +import sonia.scm.plugin.PluginManager; +import sonia.scm.plugin.PluginTestHelper; + +import static java.util.List.of; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +class PluginListAvailableCommandTest { + + private final TemplateTestRenderer templateTestRenderer = new TemplateTestRenderer(); + @Mock + private PluginManager manager; + + private PluginListAvailableCommand command; + + @BeforeEach + void initCommand() { + command = new PluginListAvailableCommand(templateTestRenderer.createTemplateRenderer(), manager); + command.setSpec(templateTestRenderer.getMockedSpec()); + PendingPlugins pendingPlugins = mock(PendingPlugins.class); + lenient().doReturn(pendingPlugins).when(manager).getPending(); + } + + @Test + void shouldListPlugins() { + doReturn(of( + PluginTestHelper.createAvailable("scm-review-plugin"), + PluginTestHelper.createAvailable("scm-test-plugin", "1.1.0")) + ).when(manager).getAvailable(); + + command.run(); + + assertThat(templateTestRenderer.getStdOut()) + .contains("NAME DISPLAY NAME AVAILABLE PENDING?") + .contains("scm-review-plugin 1.0") + .contains("scm-test-plugin 1.1.0"); + } + + @Test + void shouldListPluginsAsShortList() { + doReturn(of( + PluginTestHelper.createAvailable("scm-review-plugin"), + PluginTestHelper.createAvailable("scm-test-plugin", "1.1.0"), + PluginTestHelper.createAvailable("scm-archive-plugin")) + ).when(manager).getAvailable(); + command.setUseShortTemplate(true); + + command.run(); + + assertThat(templateTestRenderer.getStdOut()) + .isEqualTo("scm-archive-plugin\nscm-review-plugin\nscm-test-plugin\n"); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/cli/PluginListInstalledCommandTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/cli/PluginListInstalledCommandTest.java new file mode 100644 index 0000000000..fc215efece --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/plugin/cli/PluginListInstalledCommandTest.java @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.TemplateTestRenderer; +import sonia.scm.plugin.PendingPlugins; +import sonia.scm.plugin.PluginManager; +import sonia.scm.plugin.PluginTestHelper; + +import static java.util.List.of; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +class PluginListInstalledCommandTest { + + private final TemplateTestRenderer templateTestRenderer = new TemplateTestRenderer(); + @Mock + private PluginManager manager; + + private PluginListInstalledCommand command; + + @BeforeEach + void initCommand() { + command = new PluginListInstalledCommand(templateTestRenderer.createTemplateRenderer(), manager); + command.setSpec(templateTestRenderer.getMockedSpec()); + PendingPlugins pendingPlugins = mock(PendingPlugins.class); + lenient().doReturn(pendingPlugins).when(manager).getPending(); + } + + @Test + void shouldListPlugins() { + doReturn(of(PluginTestHelper.createInstalled("scm-test-plugin"), PluginTestHelper.createInstalled("scm-review-plugin", "1.1.0"))) + .when(manager).getInstalled(); + + command.run(); + + assertThat(templateTestRenderer.getStdOut()) + .contains("NAME DISPLAY NAME INSTALLED PENDING?") + .contains("scm-review-plugin 1.1.0") + .contains("scm-test-plugin 1.0"); + } + + @Test + void shouldListPluginsAsShortList() { + doReturn(of(PluginTestHelper.createInstalled("scm-test-plugin"), PluginTestHelper.createInstalled("scm-archive-plugin"))) + .when(manager).getInstalled(); + command.setUseShortTemplate(true); + + command.run(); + + assertThat(templateTestRenderer.getStdOut()) + .isEqualTo("scm-archive-plugin\nscm-test-plugin\n"); + } +}