Enable plugin management via CLI (#2087)

Co-authored-by: Konstantin Schaper <konstantin.schaper@cloudogu.com>
This commit is contained in:
Eduard Heimbuch
2022-07-19 09:02:00 +02:00
committed by GitHub
parent 67c083ee54
commit fc28da90b3
27 changed files with 1718 additions and 74 deletions

View File

@@ -0,0 +1,2 @@
- type: added
description: Enable plugin management via CLI ([#2087](https://github.com/scm-manager/scm-manager/pull/2087))

View File

@@ -0,0 +1,76 @@
/*
* 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;
import java.util.List;
import static com.google.common.collect.Iterables.contains;
import static java.util.stream.Collectors.toList;
public class PendingPlugins {
private final List<AvailablePlugin> install;
private final List<InstalledPlugin> update;
private final List<InstalledPlugin> uninstall;
public PendingPlugins(List<AvailablePlugin> availablePlugins, List<InstalledPlugin> installedPlugins) {
List<AvailablePlugin> pending = availablePlugins
.stream()
.filter(AvailablePlugin::isPending)
.collect(toList());
this.install = pending
.stream()
.filter(a -> !contains(installedPlugins, a)).collect(toList());
this.update = installedPlugins
.stream()
.filter(i -> contains(pending, i)).collect(toList());
this.uninstall = installedPlugins
.stream()
.filter(InstalledPlugin::isMarkedForUninstall).collect(toList());
}
public List<AvailablePlugin> getInstall() {
return install;
}
public List<InstalledPlugin> getUpdate() {
return update;
}
public List<InstalledPlugin> getUninstall() {
return uninstall;
}
public boolean isPending(String name) {
return uninstall.stream().anyMatch(p -> p.getDescriptor().getInformation().getName().equals(name))
|| update.stream().anyMatch(p -> p.getDescriptor().getInformation().getName().equals(name))
|| install.stream().anyMatch(p -> p.getDescriptor().getInformation().getName().equals(name));
}
public boolean existPendingChanges() {
return !uninstall.isEmpty() || !update.isEmpty() || !install.isEmpty();
}
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.plugin; package sonia.scm.plugin;
import java.util.List; import java.util.List;
@@ -89,6 +89,14 @@ public interface PluginManager {
*/ */
List<InstalledPlugin> getUpdatable(); List<InstalledPlugin> getUpdatable();
/**
* Returns all pending plugins.
*
* @return a list of pending plugins.
* @since 2.38.0
*/
PendingPlugins getPending();
/** /**
* Installs the plugin with the given name from the list of available plugins. * Installs the plugin with the given name from the list of available plugins.
* *

View File

@@ -32,8 +32,7 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.lifecycle.Restarter; import sonia.scm.lifecycle.Restarter;
import sonia.scm.plugin.AvailablePlugin; import sonia.scm.plugin.PendingPlugins;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.PluginManager; import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginPermissions; import sonia.scm.plugin.PluginPermissions;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;
@@ -44,9 +43,7 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
@@ -94,28 +91,13 @@ public class PendingPluginResource {
) )
) )
public Response getPending() { public Response getPending() {
List<AvailablePlugin> pending = pluginManager PendingPlugins pending = pluginManager.getPending();
.getAvailable()
.stream()
.filter(AvailablePlugin::isPending)
.collect(toList());
List<InstalledPlugin> installed = pluginManager.getInstalled();
Stream<AvailablePlugin> newPlugins = pending
.stream()
.filter(a -> !contains(installed, a));
Stream<InstalledPlugin> updatePlugins = installed
.stream()
.filter(i -> contains(pending, i));
Stream<InstalledPlugin> uninstallPlugins = installed
.stream()
.filter(InstalledPlugin::isMarkedForUninstall);
Links.Builder linksBuilder = linkingTo().self(resourceLinks.pendingPluginCollection().self()); Links.Builder linksBuilder = linkingTo().self(resourceLinks.pendingPluginCollection().self());
List<PluginDto> installDtos = newPlugins.map(mapper::mapAvailable).collect(toList()); List<PluginDto> installDtos = pending.getInstall().stream().map(mapper::mapAvailable).collect(toList());
List<PluginDto> updateDtos = updatePlugins.map(i -> mapper.mapInstalled(i, pending)).collect(toList()); List<PluginDto> updateDtos = pending.getUpdate().stream().map(p -> mapper.mapInstalled(p, pending.getInstall())).collect(toList());
List<PluginDto> uninstallDtos = uninstallPlugins.map(i -> mapper.mapInstalled(i, pending)).collect(toList()); List<PluginDto> uninstallDtos = pending.getUninstall().stream().map(i -> mapper.mapInstalled(i, pending.getInstall())).collect(toList());
if ( if (
PluginPermissions.write().isPermitted() && PluginPermissions.write().isPermitted() &&
@@ -135,22 +117,6 @@ public class PendingPluginResource {
return Response.ok(new HalRepresentation(linksBuilder.build(), embedded.build())).build(); return Response.ok(new HalRepresentation(linksBuilder.build(), embedded.build())).build();
} }
private boolean contains(Collection<InstalledPlugin> installedPlugins, AvailablePlugin availablePlugin) {
return installedPlugins
.stream()
.anyMatch(installedPlugin -> haveSameName(installedPlugin, availablePlugin));
}
private boolean contains(Collection<AvailablePlugin> availablePlugins, InstalledPlugin installedPlugin) {
return availablePlugins
.stream()
.anyMatch(availablePlugin -> haveSameName(installedPlugin, availablePlugin));
}
private boolean haveSameName(InstalledPlugin installedPlugin, AvailablePlugin availablePlugin) {
return installedPlugin.getDescriptor().getInformation().getName().equals(availablePlugin.getDescriptor().getInformation().getName());
}
@POST @POST
@Path("/execute") @Path("/execute")
@Operation( @Operation(

View File

@@ -41,6 +41,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
@@ -87,15 +88,11 @@ public class DefaultPluginManager implements PluginManager {
this.eventBus = eventBus; this.eventBus = eventBus;
this.pluginSetConfigStore = pluginSetConfigStore; this.pluginSetConfigStore = pluginSetConfigStore;
if (contextFactory != null) { this.contextFactory = Objects.requireNonNullElseGet(contextFactory, () -> (plugins -> {
this.contextFactory = contextFactory; List<AvailablePlugin> pendingPlugins = new ArrayList<>(plugins);
} else { pendingInstallQueue.stream().map(PendingPluginInstallation::getPlugin).forEach(pendingPlugins::add);
this.contextFactory = (plugins -> { return PluginInstallationContext.from(getInstalled(), pendingPlugins);
List<AvailablePlugin> pendingPlugins = new ArrayList<>(plugins); }));
pendingInstallQueue.stream().map(PendingPluginInstallation::getPlugin).forEach(pendingPlugins::add);
return PluginInstallationContext.from(getInstalled(), pendingPlugins);
});
}
this.computeInstallationDependencies(); this.computeInstallationDependencies();
} }
@@ -192,6 +189,7 @@ public class DefaultPluginManager implements PluginManager {
@Override @Override
public List<InstalledPlugin> getUpdatable() { public List<InstalledPlugin> getUpdatable() {
PluginPermissions.read().check();
return getInstalled() return getInstalled()
.stream() .stream()
.filter(p -> isUpdatable(p.getDescriptor().getInformation().getName())) .filter(p -> isUpdatable(p.getDescriptor().getInformation().getName()))
@@ -199,6 +197,12 @@ public class DefaultPluginManager implements PluginManager {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@Override
public PendingPlugins getPending() {
PluginPermissions.read().check();
return new PendingPlugins(getAvailable(), getInstalled());
}
private <T extends Plugin> Predicate<T> filterByName(String name) { private <T extends Plugin> Predicate<T> filterByName(String name) {
return plugin -> name.equals(plugin.getDescriptor().getInformation().getName()); return plugin -> name.equals(plugin.getDescriptor().getInformation().getName());
} }
@@ -361,7 +365,7 @@ public class DefaultPluginManager implements PluginManager {
} }
private boolean isUpdatable(String name) { private boolean isUpdatable(String name) {
return getAvailable(name).isPresent() && !getPending(name).isPresent(); return getAvailable(name).isPresent() && getPending(name).isEmpty();
} }
@Override @Override

View File

@@ -0,0 +1,90 @@
/*
* 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.cronutils.utils.VisibleForTesting;
import picocli.CommandLine;
import sonia.scm.cli.ParentCommand;
import sonia.scm.plugin.PluginManager;
import javax.inject.Inject;
@ParentCommand(value = PluginCommand.class)
@CommandLine.Command(name = "add")
class PluginAddCommand implements Runnable {
@CommandLine.Parameters(index = "0", paramLabel = "<name>", descriptionKey = "scm.plugin.name")
private String name;
@CommandLine.Option(names = {"--apply", "-a"}, descriptionKey = "scm.plugin.apply")
private boolean apply;
@CommandLine.Mixin
private final PluginTemplateRenderer templateRenderer;
private final PluginManager manager;
@CommandLine.Spec
private CommandLine.Model.CommandSpec spec;
@Inject
PluginAddCommand(PluginTemplateRenderer templateRenderer, PluginManager manager) {
this.templateRenderer = templateRenderer;
this.manager = manager;
}
@Override
public void run() {
if (manager.getInstalled(name).isPresent()) {
templateRenderer.renderPluginAlreadyInstalledError();
return;
}
if (manager.getAvailable(name).isEmpty()) {
templateRenderer.renderPluginNotAvailableError();
return;
}
try {
manager.install(name, apply);
} catch (Exception e) {
templateRenderer.renderPluginCouldNotBeAdded(name);
throw e;
}
templateRenderer.renderPluginAdded(name);
if (!apply) {
templateRenderer.renderServerRestartRequired();
} else {
templateRenderer.renderServerRestartTriggered();
}
}
@VisibleForTesting
void setName(String name) {
this.name = name;
}
@VisibleForTesting
void setApply(boolean apply) {
this.apply = apply;
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.cronutils.utils.VisibleForTesting;
import picocli.CommandLine;
import sonia.scm.cli.ParentCommand;
import sonia.scm.plugin.PluginManager;
import javax.inject.Inject;
@ParentCommand(value = PluginCommand.class)
@CommandLine.Command(name = "apply")
class PluginApplyCommand implements Runnable {
@CommandLine.Option(names = {"--yes", "-y"}, descriptionKey = "scm.plugin.restart")
private boolean restart;
@CommandLine.Mixin
private final PluginTemplateRenderer templateRenderer;
private final PluginManager manager;
@CommandLine.Spec
private CommandLine.Model.CommandSpec spec;
@Inject
PluginApplyCommand(PluginTemplateRenderer templateRenderer, PluginManager manager) {
this.templateRenderer = templateRenderer;
this.manager = manager;
}
@Override
public void run() {
if (!restart) {
templateRenderer.renderConfirmServerRestart();
return;
}
if (manager.getPending().existPendingChanges()) {
manager.executePendingAndRestart();
templateRenderer.renderServerRestartTriggered();
} else {
templateRenderer.renderSkipServerRestart();
}
}
@VisibleForTesting
void setRestart(boolean restart) {
this.restart = restart;
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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;
@CommandLine.Command(name = "plugin")
public class PluginCommand {
}

View File

@@ -0,0 +1,163 @@
/*
* 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.cronutils.utils.VisibleForTesting;
import lombok.Getter;
import lombok.Setter;
import picocli.CommandLine;
import sonia.scm.cli.ParentCommand;
import sonia.scm.cli.Table;
import sonia.scm.cli.TemplateRenderer;
import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.PendingPlugins;
import sonia.scm.plugin.PluginDescriptor;
import sonia.scm.plugin.PluginManager;
import javax.inject.Inject;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ParentCommand(value = PluginCommand.class)
@CommandLine.Command(name = "list", aliases = "ls")
class PluginListCommand implements Runnable {
@CommandLine.Mixin
private final TemplateRenderer templateRenderer;
private final PluginManager manager;
@CommandLine.Spec
private CommandLine.Model.CommandSpec spec;
@CommandLine.Option(names = {"--short", "-s"})
private boolean useShortTemplate;
private 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",
"{{#plugins}}",
"{{name}}",
"{{/plugins}}"
);
@Inject
public PluginListCommand(TemplateRenderer templateRenderer, PluginManager manager) {
this.templateRenderer = templateRenderer;
this.manager = manager;
}
@Override
public void run() {
Collection<ListablePlugin> plugins = getListablePlugins();
if (useShortTemplate) {
templateRenderer.renderToStdout(SHORT_TEMPLATE, Map.of("plugins", plugins));
} else {
Table table = templateRenderer.createTable();
String yes = spec.resourceBundle().getString("yes");
table.addHeader("scm.plugin.name", "scm.plugin.displayName", "scm.plugin.availableVersion", "scm.plugin.installedVersion", "scm.plugin.pending");
for (ListablePlugin plugin : plugins) {
table.addRow(
plugin.getName(),
plugin.getDisplayName(),
plugin.getAvailableVersion(),
plugin.getInstalledVersion(),
plugin.isPending() ? yes : ""
);
}
templateRenderer.renderToStdout(TABLE_TEMPLATE, Map.of("rows", table, "plugins", plugins));
}
}
private List<ListablePlugin> getListablePlugins() {
List<InstalledPlugin> installedPlugins = manager.getInstalled();
List<AvailablePlugin> availablePlugins = manager.getAvailable();
PendingPlugins pendingPlugins = manager.getPending();
Set<ListablePlugin> plugins = new HashSet<>();
for (PluginDescriptor pluginDesc : installedPlugins.stream().map(InstalledPlugin::getDescriptor).collect(Collectors.toList())) {
ListablePlugin listablePlugin = new ListablePlugin(pendingPlugins, pluginDesc, true);
setAvailableVersion(listablePlugin);
plugins.add(listablePlugin);
}
for (PluginDescriptor pluginDesc : availablePlugins.stream().map(AvailablePlugin::getDescriptor).collect(Collectors.toList())) {
if (plugins.stream().noneMatch(p -> p.name.equals(pluginDesc.getInformation().getName()))) {
plugins.add(new ListablePlugin(pendingPlugins, pluginDesc, false));
}
}
return plugins.stream().sorted((a, b) -> a.name.compareToIgnoreCase(b.name)).collect(Collectors.toList());
}
private void setAvailableVersion(ListablePlugin listablePlugin) {
Optional<AvailablePlugin> availablePlugin = manager.getAvailable().stream().filter(p -> p.getDescriptor().getInformation().getName().equals(listablePlugin.name)).findFirst();
if (availablePlugin.isPresent() && !availablePlugin.get().getDescriptor().getInformation().getVersion().equals(listablePlugin.installedVersion)) {
listablePlugin.setAvailableVersion(availablePlugin.get().getDescriptor().getInformation().getVersion());
}
}
@VisibleForTesting
void setSpec(CommandLine.Model.CommandSpec spec) {
this.spec = spec;
}
@VisibleForTesting
void setUseShortTemplate(boolean useShortTemplate) {
this.useShortTemplate = useShortTemplate;
}
@Getter
@Setter
static class ListablePlugin {
private String name;
private String displayName;
private String installedVersion;
private String availableVersion;
private boolean pending;
private boolean installed;
ListablePlugin(PendingPlugins pendingPlugins, PluginDescriptor descriptor, boolean installed) {
this.name = descriptor.getInformation().getName();
this.displayName = descriptor.getInformation().getDisplayName();
if (installed) {
this.installedVersion = descriptor.getInformation().getVersion();
} else {
this.availableVersion = descriptor.getInformation().getVersion();
}
this.pending = pendingPlugins.isPending(descriptor.getInformation().getName());
this.installed = installed;
}
}
}

View File

@@ -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.cronutils.utils.VisibleForTesting;
import picocli.CommandLine;
import sonia.scm.cli.ParentCommand;
import sonia.scm.plugin.PluginManager;
import javax.inject.Inject;
@ParentCommand(value = PluginCommand.class)
@CommandLine.Command(name = "remove", aliases = "rm")
class PluginRemoveCommand implements Runnable {
@CommandLine.Parameters(index = "0", paramLabel = "<name>", descriptionKey = "scm.plugin.name")
private String name;
@CommandLine.Option(names = {"--apply", "-a"}, descriptionKey = "scm.plugin.apply")
private boolean apply;
@CommandLine.Mixin
private final PluginTemplateRenderer templateRenderer;
private final PluginManager manager;
@CommandLine.Spec
private CommandLine.Model.CommandSpec spec;
@Inject
PluginRemoveCommand(PluginTemplateRenderer templateRenderer, PluginManager manager) {
this.templateRenderer = templateRenderer;
this.manager = manager;
}
@Override
public void run() {
if (manager.getInstalled(name).isEmpty()) {
templateRenderer.renderPluginNotInstalledError();
return;
}
try {
manager.uninstall(name, apply);
} catch (Exception e) {
templateRenderer.renderPluginCouldNotBeRemoved(name);
throw e;
}
templateRenderer.renderPluginRemoved(name);
if (!apply) {
templateRenderer.renderServerRestartRequired();
} else {
templateRenderer.renderServerRestartTriggered();
}
}
@VisibleForTesting
void setName(String name) {
this.name = name;
}
@VisibleForTesting
void setApply(boolean apply) {
this.apply = apply;
}
}

View File

@@ -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.plugin.PluginManager;
import javax.inject.Inject;
@ParentCommand(value = PluginCommand.class)
@CommandLine.Command(name = "cancel-pending", aliases = "reset")
class PluginResetChangesCommand implements Runnable {
@CommandLine.Mixin
private final PluginTemplateRenderer templateRenderer;
private final PluginManager manager;
@CommandLine.Spec
private CommandLine.Model.CommandSpec spec;
@Inject
PluginResetChangesCommand(PluginTemplateRenderer templateRenderer, PluginManager manager) {
this.templateRenderer = templateRenderer;
this.manager = manager;
}
@Override
public void run() {
if (manager.getPending().existPendingChanges()) {
manager.cancelPending();
templateRenderer.renderPluginsReseted();
} else {
templateRenderer.renderNoPendingPlugins();
}
}
}

View File

@@ -0,0 +1,157 @@
/*
* 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 sonia.scm.cli.CliContext;
import sonia.scm.cli.ExitCode;
import sonia.scm.cli.TemplateRenderer;
import sonia.scm.template.TemplateEngineFactory;
import javax.inject.Inject;
import java.util.Collections;
import java.util.Map;
class PluginTemplateRenderer extends TemplateRenderer {
private static final String PLUGIN_NOT_AVAILABLE_ERROR_TEMPLATE = "{{i18n.scmPluginNotAvailable}}";
private static final String PLUGIN_NOT_INSTALLED_ERROR_TEMPLATE = "{{i18n.scmPluginNotInstalled}}";
private static final String PLUGIN_ALREADY_INSTALLED_ERROR_TEMPLATE = "{{i18n.scmPluginAlreadyInstalled}}";
private static final String PLUGIN_NOT_REMOVED_ERROR_TEMPLATE = "{{i18n.scmPluginNotRemoved}}";
private static final String PLUGIN_NOT_ADDED_ERROR_TEMPLATE = "{{i18n.scmPluginNotAdded}}";
private static final String PLUGIN_NOT_UPDATABLE_ERROR_TEMPLATE = "{{i18n.scmPluginNotUpdatable}}";
private static final String PLUGIN_UPDATE_ERROR_TEMPLATE = "{{i18n.scmPluginsUpdateFailed}}";
private static final String PLUGIN_ADDED_TEMPLATE = "{{i18n.scmPluginAdded}}";
private static final String PLUGIN_REMOVED_TEMPLATE = "{{i18n.scmPluginRemoved}}";
private static final String PLUGIN_UPDATED_TEMPLATE = "{{i18n.scmPluginUpdated}}";
private static final String ALL_PLUGINS_UPDATED_TEMPLATE = "{{i18n.scmPluginsUpdated}}";
private static final String SERVER_RESTART_REQUIRED_TEMPLATE = "{{i18n.scmServerRestartRequired}}";
private static final String SERVER_RESTART_TRIGGERED_TEMPLATE = "{{i18n.scmServerRestartTriggered}}";
private static final String SERVER_RESTART_SKIPPED_TEMPLATE = "{{i18n.scmServerRestartSkipped}}";
private static final String SERVER_RESTART_CONFIRMATION_TEMPLATE = "{{i18n.scmServerRestartConfirmation}}";
private static final String PLUGINS_NOT_PENDING_ERROR_TEMPLATE = "{{i18n.scmPluginsNotPending}}";
private static final String ALL_PENDING_PLUGINS_CANCELLED = "{{i18n.scmPendingPluginsCancelled}}";
private static final String PLUGIN = "plugin";
private final CliContext context;
@Inject
PluginTemplateRenderer(CliContext context, TemplateEngineFactory templateEngineFactory) {
super(context, templateEngineFactory);
this.context = context;
}
public void renderPluginAdded(String pluginName) {
renderToStdout(PLUGIN_ADDED_TEMPLATE, Map.of(PLUGIN, pluginName));
context.getStdout().println();
}
public void renderPluginRemoved(String pluginName) {
renderToStdout(PLUGIN_REMOVED_TEMPLATE, Map.of(PLUGIN, pluginName));
context.getStdout().println();
}
public void renderPluginUpdated(String pluginName) {
renderToStdout(PLUGIN_UPDATED_TEMPLATE, Map.of(PLUGIN, pluginName));
context.getStdout().println();
}
public void renderAllPluginsUpdated() {
renderToStdout(ALL_PLUGINS_UPDATED_TEMPLATE, Collections.emptyMap());
context.getStdout().println();
}
public void renderPluginCouldNotBeRemoved(String pluginName) {
renderToStderr(PLUGIN_NOT_REMOVED_ERROR_TEMPLATE, Map.of(PLUGIN, pluginName));
context.getStderr().println();
context.exit(ExitCode.USAGE);
}
public void renderPluginCouldNotBeAdded(String pluginName) {
renderToStderr(PLUGIN_NOT_ADDED_ERROR_TEMPLATE, Map.of(PLUGIN, pluginName));
context.getStderr().println();
context.exit(ExitCode.SERVER_ERROR);
}
public void renderPluginNotUpdatable(String pluginName) {
renderToStderr(PLUGIN_NOT_UPDATABLE_ERROR_TEMPLATE, Map.of(PLUGIN, pluginName));
context.getStderr().println();
context.exit(ExitCode.USAGE);
}
public void renderServerRestartRequired() {
renderToStdout(SERVER_RESTART_REQUIRED_TEMPLATE, Collections.emptyMap());
context.getStdout().println();
}
public void renderServerRestartTriggered() {
renderToStdout(SERVER_RESTART_TRIGGERED_TEMPLATE, Collections.emptyMap());
context.getStdout().println();
}
public void renderSkipServerRestart() {
renderToStdout(SERVER_RESTART_SKIPPED_TEMPLATE, Collections.emptyMap());
context.getStdout().println();
}
public void renderPluginsReseted() {
renderToStdout(ALL_PENDING_PLUGINS_CANCELLED, Collections.emptyMap());
context.getStdout().println();
}
public void renderNoPendingPlugins() {
renderToStderr(PLUGINS_NOT_PENDING_ERROR_TEMPLATE, Collections.emptyMap());
context.getStderr().println();
}
public void renderConfirmServerRestart() {
renderToStderr(SERVER_RESTART_CONFIRMATION_TEMPLATE, Collections.emptyMap());
context.getStderr().println();
context.exit(ExitCode.USAGE);
}
public void renderPluginNotAvailableError() {
renderToStderr(PLUGIN_NOT_AVAILABLE_ERROR_TEMPLATE, Collections.emptyMap());
context.getStderr().println();
context.exit(ExitCode.USAGE);
}
public void renderPluginsUpdateError() {
renderToStderr(PLUGIN_UPDATE_ERROR_TEMPLATE, Collections.emptyMap());
context.getStderr().println();
context.exit(ExitCode.SERVER_ERROR);
}
public void renderPluginNotInstalledError() {
renderToStderr(PLUGIN_NOT_INSTALLED_ERROR_TEMPLATE, Collections.emptyMap());
context.getStderr().println();
context.exit(ExitCode.USAGE);
}
public void renderPluginAlreadyInstalledError() {
renderToStderr(PLUGIN_ALREADY_INSTALLED_ERROR_TEMPLATE, Collections.emptyMap());
context.getStderr().println();
context.exit(ExitCode.USAGE);
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.cronutils.utils.VisibleForTesting;
import picocli.CommandLine;
import sonia.scm.cli.ParentCommand;
import sonia.scm.plugin.PluginManager;
import javax.inject.Inject;
@ParentCommand(value = PluginCommand.class)
@CommandLine.Command(name = "update-all")
class PluginUpdateAllCommand implements Runnable {
@CommandLine.Option(names = {"--apply", "-a"}, descriptionKey = "scm.plugin.apply")
private boolean apply;
@CommandLine.Mixin
private final PluginTemplateRenderer templateRenderer;
private final PluginManager manager;
@CommandLine.Spec
private CommandLine.Model.CommandSpec spec;
@Inject
PluginUpdateAllCommand(PluginTemplateRenderer templateRenderer, PluginManager manager) {
this.templateRenderer = templateRenderer;
this.manager = manager;
}
@Override
public void run() {
try {
manager.updateAll();
} catch (Exception e) {
templateRenderer.renderPluginsUpdateError();
throw e;
}
templateRenderer.renderAllPluginsUpdated();
if (!apply) {
templateRenderer.renderServerRestartRequired();
} else {
manager.executePendingAndRestart();
templateRenderer.renderServerRestartTriggered();
}
}
@VisibleForTesting
void setApply(boolean apply) {
this.apply = apply;
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.cronutils.utils.VisibleForTesting;
import picocli.CommandLine;
import sonia.scm.cli.ParentCommand;
import sonia.scm.plugin.PluginManager;
import javax.inject.Inject;
import java.util.Objects;
@ParentCommand(value = PluginCommand.class)
@CommandLine.Command(name = "update")
class PluginUpdateCommand implements Runnable {
@CommandLine.Parameters(index = "0", paramLabel = "<name>", descriptionKey = "scm.plugin.name")
private String name;
@CommandLine.Option(names = {"--apply", "-a"}, descriptionKey = "scm.plugin.apply")
private boolean apply;
@CommandLine.Mixin
private final PluginTemplateRenderer templateRenderer;
private final PluginManager manager;
@CommandLine.Spec
private CommandLine.Model.CommandSpec spec;
@Inject
PluginUpdateCommand(PluginTemplateRenderer templateRenderer, PluginManager manager) {
this.templateRenderer = templateRenderer;
this.manager = manager;
}
@Override
public void run() {
if (manager.getInstalled(name).isEmpty()) {
templateRenderer.renderPluginNotInstalledError();
return;
}
if (manager.getUpdatable().stream().noneMatch(p -> Objects.equals(p.getDescriptor().getInformation().getName(), name))) {
templateRenderer.renderPluginNotUpdatable(name);
return;
}
manager.install(name, apply);
templateRenderer.renderPluginUpdated(name);
if (!apply) {
templateRenderer.renderServerRestartRequired();
} else {
templateRenderer.renderServerRestartTriggered();
}
}
@VisibleForTesting
void setName(String name) {
this.name = name;
}
@VisibleForTesting
void setApply(boolean apply) {
this.apply = apply;
}
}

View File

@@ -189,3 +189,57 @@ scm.group.remove-member.name = Name of the group
scm.group.remove-member.members = Members to remove from the group scm.group.remove-member.members = Members to remove from the group
groupNotFound = Could not find group groupNotFound = Could not find group
## Plugin
scm.plugin.usage.description.0 = Resource command for all plugin-management-related actions
scmPluginNotAvailable = This plugin is not available. Please check your input.
scmPluginNotInstalled = This plugin is not installed. Please check your input.
scmPluginAlreadyInstalled = This plugin is already installed.
scmPluginAdded = The plugin was added for installation.
scmPluginRemoved = The plugin was removed.
scmPluginUpdated = The plugin was updated.
scmPluginsUpdated = All available plugins were updated.
scmPluginNotRemoved = This plugin could not be removed. Other installed plugins could depend on this plugin.
scmPluginNotAdded = This plugin could not be added. Please check your server logs for further information.
scmPluginNotUpdatable = This plugin could not be updated.
scmPluginsUpdateFailed = Plugin updated failed. Please check your server logs for further information.
scmServerRestartRequired = Please restart your server to make the plugin changes effective.
scmServerRestartTriggered = Server restart: Applying the changes...
scmServerRestartSkipped = Server restart skipped: Nothing to apply.
scmServerRestartConfirmation = Please confirm the server restart using --yes.
scmPluginsNotPending = No pending plugin changes found.
scmPendingPluginsCancelled = All plugin changes were cancelled.
### List plugins
scm.plugin.name = Name
scm.plugin.displayName = Display Name
scm.plugin.installedVersion = Installed
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
### Add plugin
scm.plugin.add.usage.description.0 = Installs the plugin with the required dependencies
scm.plugin.add.apply = Apply all plugin changes now by restarting the server
### Remove plugin
scm.plugin.remove.usage.description.0 = Deletes the plugin
scm.plugin.remove.apply = Apply all plugin changes now by restarting the server
### Update plugin
scm.plugin.update.usage.description.0 = Updates the installed plugin
scm.plugin.update.apply = Apply all plugin changes now by restarting the server
### Update all plugins
scm.plugin.update-all.usage.description.0 = Updates all installed plugins
scm.plugin.update-all.apply = Apply all plugins changes now by restarting the server
### Apply plugin changes
scm.plugin.apply.usage.description.0 = Applies the pending plugin changes by restarting the server
scm.plugin.restart = Confirms server restart
### Apply plugin changes
scm.plugin.cancel-pending.usage.description.0 = Cancel all pending plugin changes

View File

@@ -199,3 +199,56 @@ scm.group.remove-member.name = Name der Gruppe
scm.group.remove-member.members = Zu l<>schende Mitglieder scm.group.remove-member.members = Zu l<>schende Mitglieder
groupNotFound = Gruppe konnte nicht gefunden werden groupNotFound = Gruppe konnte nicht gefunden werden
## Plugin
scm.plugin.usage.description.0 = Ressourcenkommando f<>r alle Pluginverwaltungsaktionen
scmPluginNotAvailable = Dieses Plugin ist nicht verf<72>gbar. Bitte pr<70>fen Sie Ihre Eingabe.
scmPluginNotInstalled = Dieses Plugin ist nicht installiert. Bitte pr<70>fen Sie Ihre Eingabe.
scmPluginAlreadyInstalled = Dieses Plugin ist bereits installiert.
scmPluginAdded = Das Plugin wurde hinzugef<65>gt.
scmPluginRemoved = Das Plugin wurde entfernt.
scmPluginUpdated = Das Plugin wurde aktualisiert.
scmPluginsUpdated = Alle verf<72>gbaren Plugins wurden aktualisiert.
scmPluginNotRemoved = Dieses Plugin konnte nicht entfernt werden. Andere installierte Plugins k<>nnten von diesem Plugin abh<62>ngen.
scmPluginNotAdded = Dieses Plugin konnte nicht hinzugef<65>gt werden. Weitere Informationen finden Sie in den Server Logs.
scmPluginNotUpdatable = Dieses Plugin konnte nicht aktualisiert werden.
scmPluginsUpdateFailed = Plugin Aktualisierungen fehlgeschlagen. Weitere Informationen finden Sie in den Server Logs.
scmServerRestartRequired = Bitte starten Sie Ihren Server neu, um die Plugin <20>nderungen wirksam zu machen.
scmServerRestartTriggered = Server Neustart: <20>nderungen werden <20>bernommen...
scmServerRestartSkipped = Server Neustart <20>bersprungen: Keine <20>nderungen gefunden.
scmServerRestartConfirmation = Bitte best<73>tigen Sie den Server Neustart mit --yes.
scmPluginsNotPending = Keine ausstehenden Plugin <20>nderungen gefunden.
scmPendingPluginsCancelled = Alle ausstehenden Plugin <20>nderungen wurden zur<75>ckgesetzt.
### List plugins
scm.plugin.name = Name
scm.plugin.displayName = Anzeigename
scm.plugin.installedVersion = Installiert
scm.plugin.availableVersion = Verf<EFBFBD>gbar
scm.plugin.pending = <EFBFBD>nderung ausstehend
scm.plugin.list.usage.description.0 = Listet alle Plugins mit Versionen auf
scm.plugin.list.short = Zeigt nur die Plugin Namen an
### Add plugin
scm.plugin.add.usage.description.0 = Installiert das Plugin inklusive Abh<62>ngigkeiten
scm.plugin.add.apply = Macht alle Plugin <20>nderungen sofort wirksam (Server Neustart!)
### Remove plugin
scm.plugin.remove.usage.description.0 = Entfernt das Plugin
scm.plugin.remove.apply = Macht alle Plugin <20>nderungen sofort wirksam (Server Neustart!)
### Update plugin
scm.plugin.update.usage.description.0 = Aktualisiert das installierte Plugin
scm.plugin.update.apply = Macht alle Plugin <20>nderungen sofort wirksam (Server Neustart!)
### Update all plugins
scm.plugin.update-all.usage.description.0 = Aktualisiert alle installierten Plugins
scm.plugin.update-all.apply = Macht alle Plugin <20>nderungen sofort wirksam (Server Neustart!)
### Apply plugin changes
scm.plugin.apply.usage.description.0 = Macht alle ausstehenden Plugin <20>nderungen wirksam (Server Neustart!)
scm.plugin.restart = Server Neustart best<73>tigen
### Apply plugin changes
scm.plugin.cancel-pending.usage.description.0 = Setzt alle ausstehenden Plugin <20>nderungen zur<75>ck

View File

@@ -43,6 +43,7 @@ import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.AvailablePluginDescriptor; import sonia.scm.plugin.AvailablePluginDescriptor;
import sonia.scm.plugin.InstalledPlugin; import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.InstalledPluginDescriptor; import sonia.scm.plugin.InstalledPluginDescriptor;
import sonia.scm.plugin.PendingPlugins;
import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginManager; import sonia.scm.plugin.PluginManager;
import sonia.scm.web.RestDispatcher; import sonia.scm.web.RestDispatcher;
@@ -65,7 +66,7 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class PendingPluginResourceTest { class PendingPluginResourceTest {
private RestDispatcher dispatcher = new RestDispatcher(); private final RestDispatcher dispatcher = new RestDispatcher();
@SuppressWarnings("unused") @SuppressWarnings("unused")
ResourceLinks resourceLinks = ResourceLinksMock.createMock(create("/")); ResourceLinks resourceLinks = ResourceLinksMock.createMock(create("/"));
@@ -125,23 +126,22 @@ class PendingPluginResourceTest {
@Test @Test
void shouldGetEmptyPluginListsWithoutInstallLinkWhenNoPendingPluginsPresent() throws URISyntaxException, UnsupportedEncodingException { void shouldGetEmptyPluginListsWithoutInstallLinkWhenNoPendingPluginsPresent() throws URISyntaxException, UnsupportedEncodingException {
AvailablePlugin availablePlugin = createAvailablePlugin("not-pending-plugin"); PendingPlugins pendingPlugins = mock(PendingPlugins.class);
when(availablePlugin.isPending()).thenReturn(false); when(pluginManager.getPending()).thenReturn(pendingPlugins);
when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin));
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending");
dispatcher.invoke(request, response); dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(response.getContentAsString()).contains("\"_links\":{\"self\":{\"href\":\"/v2/plugins/pending\"}}"); assertThat(response.getContentAsString()).contains("\"_links\":{\"self\":{\"href\":\"/v2/plugins/pending\"}}");
assertThat(response.getContentAsString()).doesNotContain("not-pending-plugin");
} }
@Test @Test
void shouldGetPendingAvailablePluginListWithInstallAndCancelLink() throws URISyntaxException, UnsupportedEncodingException { void shouldGetPendingAvailablePluginListWithInstallAndCancelLink() throws URISyntaxException, UnsupportedEncodingException {
AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin");
when(availablePlugin.isPending()).thenReturn(true); PendingPlugins pendingPlugins = mock(PendingPlugins.class);
when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); when(pluginManager.getPending()).thenReturn(pendingPlugins);
when(pendingPlugins.getInstall()).thenReturn(singletonList(availablePlugin));
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending");
dispatcher.invoke(request, response); dispatcher.invoke(request, response);
@@ -155,10 +155,11 @@ class PendingPluginResourceTest {
@Test @Test
void shouldGetPendingUpdatePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { void shouldGetPendingUpdatePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException {
AvailablePlugin availablePlugin = createAvailablePlugin("available-plugin"); AvailablePlugin availablePlugin = createAvailablePlugin("available-plugin");
when(availablePlugin.isPending()).thenReturn(true);
when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin));
InstalledPlugin installedPlugin = createInstalledPlugin("available-plugin"); InstalledPlugin installedPlugin = createInstalledPlugin("available-plugin");
when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); PendingPlugins pendingPlugins = mock(PendingPlugins.class);
when(pluginManager.getPending()).thenReturn(pendingPlugins);
when(pendingPlugins.getUpdate()).thenReturn(singletonList(installedPlugin));
when(pendingPlugins.getInstall()).thenReturn(singletonList(availablePlugin));
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending");
dispatcher.invoke(request, response); dispatcher.invoke(request, response);
@@ -170,10 +171,10 @@ class PendingPluginResourceTest {
@Test @Test
void shouldGetPendingUninstallPluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { void shouldGetPendingUninstallPluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException {
when(pluginManager.getAvailable()).thenReturn(emptyList());
InstalledPlugin installedPlugin = createInstalledPlugin("uninstalled-plugin"); InstalledPlugin installedPlugin = createInstalledPlugin("uninstalled-plugin");
when(installedPlugin.isMarkedForUninstall()).thenReturn(true); PendingPlugins pendingPlugins = mock(PendingPlugins.class);
when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); when(pendingPlugins.getUninstall()).thenReturn(singletonList(installedPlugin));
when(pluginManager.getPending()).thenReturn(pendingPlugins);
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending");
dispatcher.invoke(request, response); dispatcher.invoke(request, response);
@@ -187,10 +188,10 @@ class PendingPluginResourceTest {
void shouldNotReturnExecuteLinkIfRestartIsNotSupported() throws URISyntaxException, UnsupportedEncodingException { void shouldNotReturnExecuteLinkIfRestartIsNotSupported() throws URISyntaxException, UnsupportedEncodingException {
when(restarter.isSupported()).thenReturn(false); when(restarter.isSupported()).thenReturn(false);
when(pluginManager.getAvailable()).thenReturn(emptyList());
InstalledPlugin installedPlugin = createInstalledPlugin("uninstalled-plugin"); InstalledPlugin installedPlugin = createInstalledPlugin("uninstalled-plugin");
when(installedPlugin.isMarkedForUninstall()).thenReturn(true); PendingPlugins pendingPlugins = mock(PendingPlugins.class);
when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); when(pluginManager.getPending()).thenReturn(pendingPlugins);
when(pendingPlugins.getUninstall()).thenReturn(singletonList(installedPlugin));
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending");
dispatcher.invoke(request, response); dispatcher.invoke(request, response);
@@ -239,8 +240,9 @@ class PendingPluginResourceTest {
@Test @Test
void shouldGetPendingAvailablePluginListWithoutInstallAndCancelLink() throws URISyntaxException, UnsupportedEncodingException { void shouldGetPendingAvailablePluginListWithoutInstallAndCancelLink() throws URISyntaxException, UnsupportedEncodingException {
AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin");
when(availablePlugin.isPending()).thenReturn(true); PendingPlugins pendingPlugins = mock(PendingPlugins.class);
when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); when(pluginManager.getPending()).thenReturn(pendingPlugins);
when(pendingPlugins.getInstall()).thenReturn(singletonList(availablePlugin));
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending");
dispatcher.invoke(request, response); dispatcher.invoke(request, response);

View File

@@ -108,7 +108,7 @@ public class TemplateTestRenderer {
return templateEngineFactory; return templateEngineFactory;
} }
public CommandLine.Model.CommandSpec getMockedSpeck() { public CommandLine.Model.CommandSpec getMockedSpec() {
ResourceBundle resourceBundle = getResourceBundle(); ResourceBundle resourceBundle = getResourceBundle();
CommandLine.Model.CommandSpec mock = mock(CommandLine.Model.CommandSpec.class); CommandLine.Model.CommandSpec mock = mock(CommandLine.Model.CommandSpec.class);
lenient().when(mock.resourceBundle()).thenReturn(resourceBundle); lenient().when(mock.resourceBundle()).thenReturn(resourceBundle);

View File

@@ -51,7 +51,7 @@ class GroupListCommandTest {
@BeforeEach @BeforeEach
void initCommand() { void initCommand() {
command = new GroupListCommand(testRenderer.createTemplateRenderer(), manager, beanMapper); command = new GroupListCommand(testRenderer.createTemplateRenderer(), manager, beanMapper);
command.setSpec(testRenderer.getMockedSpeck()); command.setSpec(testRenderer.getMockedSpec());
} }
@BeforeEach @BeforeEach

View File

@@ -0,0 +1,118 @@
/*
* 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.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginTestHelper;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class PluginAddCommandTest {
@Mock
private PluginTemplateRenderer templateRenderer;
@Mock
private PluginManager manager;
@InjectMocks
private PluginAddCommand command;
@Test
void shouldAddPlugin() {
String pluginName = "scm-test-plugin";
doReturn(Optional.empty()).when(manager).getInstalled(pluginName);
doReturn(Optional.of(PluginTestHelper.createAvailable(pluginName))).when(manager).getAvailable(pluginName);
command.setName(pluginName);
command.run();
verify(manager).install(pluginName, false);
verify(templateRenderer).renderPluginAdded(pluginName);
verify(templateRenderer).renderServerRestartRequired();
}
@Test
void shouldAddPluginWithRestart() {
String pluginName = "scm-test-plugin";
doReturn(Optional.empty()).when(manager).getInstalled(pluginName);
doReturn(Optional.of(PluginTestHelper.createAvailable(pluginName))).when(manager).getAvailable(pluginName);
command.setName(pluginName);
command.setApply(true);
command.run();
verify(manager).install(pluginName, true);
verify(templateRenderer).renderPluginAdded(pluginName);
verify(templateRenderer).renderServerRestartTriggered();
}
@Test
void shouldRenderErrorIfPluginAlreadyInstalled() {
String pluginName = "scm-test-plugin";
doReturn(Optional.of(PluginTestHelper.createInstalled(pluginName))).when(manager).getInstalled(pluginName);
command.setName(pluginName);
command.run();
verify(templateRenderer).renderPluginAlreadyInstalledError();
}
@Test
void shouldRenderErrorIfPluginNotAvailable() {
String pluginName = "scm-test-plugin";
doReturn(Optional.empty()).when(manager).getInstalled(pluginName);
doReturn(Optional.empty()).when(manager).getAvailable(pluginName);
command.setName(pluginName);
command.run();
verify(templateRenderer).renderPluginNotAvailableError();
}
@Test
void shouldRenderErrorIfPluginInstallationFailed() {
String pluginName = "scm-test-plugin";
doReturn(Optional.empty()).when(manager).getInstalled(pluginName);
doReturn(Optional.of(PluginTestHelper.createAvailable(pluginName))).when(manager).getAvailable(pluginName);
doThrow(RuntimeException.class).when(manager).install(pluginName, false);
command.setName(pluginName);
assertThrows(RuntimeException.class, () -> command.run());
verify(templateRenderer).renderPluginCouldNotBeAdded(pluginName);
}
}

View File

@@ -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.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.plugin.PendingPlugins;
import sonia.scm.plugin.PluginManager;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class PluginApplyCommandTest {
@Mock
private PluginTemplateRenderer templateRenderer;
@Mock
private PluginManager manager;
@InjectMocks
private PluginApplyCommand command;
@Test
void shouldRestartServer() {
command.setRestart(true);
PendingPlugins pendingPlugins = mock(PendingPlugins.class);
when(manager.getPending()).thenReturn(pendingPlugins);
when(pendingPlugins.existPendingChanges()).thenReturn(true);
command.run();
verify(manager).executePendingAndRestart();
verify(templateRenderer).renderServerRestartTriggered();
}
@Test
void shouldNotRestartServerIfFlagIsMissing() {
command.run();
verify(manager, never()).executePendingAndRestart();
verify(templateRenderer).renderConfirmServerRestart();
}
@Test
void shouldNotRestartServerIfNoPendingChanges() {
command.setRestart(true);
PendingPlugins pendingPlugins = mock(PendingPlugins.class);
when(manager.getPending()).thenReturn(pendingPlugins);
when(pendingPlugins.existPendingChanges()).thenReturn(false);
command.run();
verify(manager, never()).executePendingAndRestart();
verify(templateRenderer).renderSkipServerRestart();
}
}

View File

@@ -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.mock;
@ExtendWith(MockitoExtension.class)
class PluginListCommandTest {
private final TemplateTestRenderer templateTestRenderer = new TemplateTestRenderer();
@Mock
private PluginManager manager;
private PluginListCommand command;
@BeforeEach
void initCommand() {
command = new PluginListCommand(templateTestRenderer.createTemplateRenderer(), manager);
command.setSpec(templateTestRenderer.getMockedSpec());
PendingPlugins pendingPlugins = mock(PendingPlugins.class);
doReturn(pendingPlugins).when(manager).getPending();
}
@Test
void shouldListPlugins() {
doReturn(of(PluginTestHelper.createInstalled("scm-test-plugin"))).when(manager).getInstalled();
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 INSTALLED PENDING?")
.contains("scm-review-plugin 1.0")
.contains("scm-test-plugin 1.1.0 1.0");
}
@Test
void shouldListPluginsAsShortList() {
doReturn(of(PluginTestHelper.createInstalled("scm-test-plugin"), PluginTestHelper.createInstalled("scm-archive-plugin"))).when(manager).getInstalled();
doReturn(of(
PluginTestHelper.createAvailable("scm-review-plugin"),
PluginTestHelper.createAvailable("scm-test-plugin", "1.1.0"))
).when(manager).getAvailable();
command.setUseShortTemplate(true);
command.run();
assertThat(templateTestRenderer.getStdOut())
.isEqualTo("scm-archive-plugin\nscm-review-plugin\nscm-test-plugin\n");
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginTestHelper;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class PluginRemoveCommandTest {
@Mock
private PluginTemplateRenderer templateRenderer;
@Mock
private PluginManager manager;
@InjectMocks
private PluginRemoveCommand command;
@Test
void shouldRemovePlugin() {
String pluginName = "scm-test-plugin";
command.setName(pluginName);
doReturn(Optional.of(PluginTestHelper.createInstalled(pluginName))).when(manager).getInstalled(pluginName);
command.run();
verify(manager).uninstall(pluginName, false);
verify(templateRenderer).renderPluginRemoved(pluginName);
verify(templateRenderer).renderServerRestartRequired();
}
@Test
void shouldRemovePluginWithRestart() {
String pluginName = "scm-test-plugin";
command.setName(pluginName);
command.setApply(true);
doReturn(Optional.of(PluginTestHelper.createInstalled(pluginName))).when(manager).getInstalled(pluginName);
command.run();
verify(manager).uninstall(pluginName, true);
verify(templateRenderer).renderPluginRemoved(pluginName);
verify(templateRenderer).renderServerRestartTriggered();
}
@Test
void shouldRenderErrorIfPluginNotInstalled() {
String pluginName = "scm-test-plugin";
command.setName(pluginName);
doReturn(Optional.empty()).when(manager).getInstalled(pluginName);
command.run();
verify(manager, never()).uninstall(eq(pluginName), anyBoolean());
verify(templateRenderer).renderPluginNotInstalledError();
}
@Test
void shouldRenderErrorIfPluginCouldNotBeRemoved() {
String pluginName = "scm-test-plugin";
command.setName(pluginName);
doThrow(RuntimeException.class).when(manager).uninstall(pluginName, false);
doReturn(Optional.of(PluginTestHelper.createInstalled(pluginName))).when(manager).getInstalled(pluginName);
assertThrows(RuntimeException.class, () -> command.run());
verify(templateRenderer).renderPluginCouldNotBeRemoved(pluginName);
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.plugin.PendingPlugins;
import sonia.scm.plugin.PluginManager;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class PluginResetChangesCommandTest {
@Mock
private PluginTemplateRenderer templateRenderer;
@Mock
private PluginManager manager;
@InjectMocks
private PluginResetChangesCommand command;
@Test
void shouldCancelPendingPlugins() {
PendingPlugins pendingPlugins = mock(PendingPlugins.class);
when(manager.getPending()).thenReturn(pendingPlugins);
when(pendingPlugins.existPendingChanges()).thenReturn(true);
command.run();
verify(manager).cancelPending();
verify(templateRenderer).renderPluginsReseted();
}
@Test
void shouldRenderErrorIfNoPendingPlugins() {
PendingPlugins pendingPlugins = mock(PendingPlugins.class);
when(manager.getPending()).thenReturn(pendingPlugins);
when(pendingPlugins.existPendingChanges()).thenReturn(false);
command.run();
verify(manager, never()).cancelPending();
verify(templateRenderer).renderNoPendingPlugins();
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.plugin.PluginManager;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class PluginUpdateAllCommandTest {
@Mock
private PluginTemplateRenderer templateRenderer;
@Mock
private PluginManager pluginManager;
@InjectMocks
private PluginUpdateAllCommand command;
@Test
void shouldUpdateAll() {
command.run();
verify(pluginManager).updateAll();
verify(templateRenderer).renderAllPluginsUpdated();
verify(templateRenderer).renderServerRestartRequired();
}
@Test
void shouldUpdateAllWithRestart() {
command.setApply(true);
command.run();
verify(pluginManager).updateAll();
verify(pluginManager).executePendingAndRestart();
verify(templateRenderer).renderAllPluginsUpdated();
verify(templateRenderer).renderServerRestartTriggered();
}
@Test
void shouldRenderErrorIfUpdateFailed() {
doThrow(RuntimeException.class).when(pluginManager).updateAll();
assertThrows(RuntimeException.class, () -> command.run());
verify(templateRenderer).renderPluginsUpdateError();
}
}

View File

@@ -0,0 +1,114 @@
/*
* 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.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginTestHelper;
import java.util.Optional;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class PluginUpdateCommandTest {
@Mock
private PluginTemplateRenderer templateRenderer;
@Mock
private PluginManager manager;
@InjectMocks
private PluginUpdateCommand command;
@Test
void shouldUpdateSinglePlugin() {
String pluginName = "scm-test-plugin";
doReturn(Optional.of(PluginTestHelper.createInstalled(pluginName))).when(manager).getInstalled(pluginName);
doReturn(singletonList(PluginTestHelper.createInstalled(pluginName))).when(manager).getUpdatable();
command.setName(pluginName);
command.run();
verify(manager).install(pluginName, false);
verify(templateRenderer).renderPluginUpdated(pluginName);
verify(templateRenderer).renderServerRestartRequired();
}
@Test
void shouldUpdateSinglePluginWithRestart() {
String pluginName = "scm-test-plugin";
doReturn(Optional.of(PluginTestHelper.createInstalled(pluginName))).when(manager).getInstalled(pluginName);
doReturn(singletonList(PluginTestHelper.createInstalled(pluginName))).when(manager).getUpdatable();
command.setName(pluginName);
command.setApply(true);
command.run();
verify(manager).install(pluginName, true);
verify(templateRenderer).renderPluginUpdated(pluginName);
verify(templateRenderer).renderServerRestartTriggered();
}
@Test
void shouldRenderErrorIfPluginNotInstalled() {
String pluginName = "scm-test-plugin";
doReturn(Optional.empty()).when(manager).getInstalled(pluginName);
command.setName(pluginName);
command.run();
verify(manager, never()).install(eq(pluginName), anyBoolean());
verify(templateRenderer).renderPluginNotInstalledError();
}
@Test
void shouldRenderErrorIfPluginNotUpdatable() {
String pluginName = "scm-test-plugin";
doReturn(Optional.of(PluginTestHelper.createInstalled(pluginName))).when(manager).getInstalled(pluginName);
doReturn(emptyList()).when(manager).getUpdatable();
command.setName(pluginName);
command.run();
verify(manager, never()).install(eq(pluginName), anyBoolean());
verify(templateRenderer).renderPluginNotUpdatable(pluginName);
}
}

View File

@@ -69,7 +69,7 @@ class UserListCommandTest {
@Test @Test
void shouldRenderShortTableInEnglish() { void shouldRenderShortTableInEnglish() {
testRenderer.setLocale("en"); testRenderer.setLocale("en");
command.setSpec(testRenderer.getMockedSpeck()); command.setSpec(testRenderer.getMockedSpec());
command.setUseShortTemplate(true); command.setUseShortTemplate(true);
command.run(); command.run();
@@ -81,7 +81,7 @@ class UserListCommandTest {
@Test @Test
void shouldRenderShortTableInGerman() { void shouldRenderShortTableInGerman() {
testRenderer.setLocale("de"); testRenderer.setLocale("de");
command.setSpec(testRenderer.getMockedSpeck()); command.setSpec(testRenderer.getMockedSpec());
command.setUseShortTemplate(true); command.setUseShortTemplate(true);
command.run(); command.run();
@@ -93,7 +93,7 @@ class UserListCommandTest {
@Test @Test
void shouldRenderLongTableInEnglish() { void shouldRenderLongTableInEnglish() {
testRenderer.setLocale("en"); testRenderer.setLocale("en");
command.setSpec(testRenderer.getMockedSpeck()); command.setSpec(testRenderer.getMockedSpec());
command.run(); command.run();
@@ -107,7 +107,7 @@ class UserListCommandTest {
@Test @Test
void shouldRenderLongTableInGerman() { void shouldRenderLongTableInGerman() {
testRenderer.setLocale("de"); testRenderer.setLocale("de");
command.setSpec(testRenderer.getMockedSpeck()); command.setSpec(testRenderer.getMockedSpec());
command.run(); command.run();