mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-03-06 12:20:56 +01:00
Merge pull request #1260 from scm-manager/feature/update-optional-dependencies
Feature/update optional dependencies
This commit is contained in:
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Add branch link provider to access branch links in plugins ([#1243](https://github.com/scm-manager/scm-manager/pull/1243))
|
||||
- Add key value input field component ([#1246](https://github.com/scm-manager/scm-manager/pull/1246))
|
||||
- Add Jexl parser ([#1251](https://github.com/scm-manager/scm-manager/pull/1251))
|
||||
- Update installed optional plugin dependencies upon plugin upgrade ([#1260](https://github.com/scm-manager/scm-manager/pull/1260))
|
||||
|
||||
### Changed
|
||||
- Adding start delay to liveness and readiness probes in helm chart template
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -560,7 +560,7 @@
|
||||
<plugin>
|
||||
<groupId>sonia.scm.maven</groupId>
|
||||
<artifactId>smp-maven-plugin</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
|
||||
@@ -21,12 +21,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@@ -35,13 +37,23 @@ public class AvailablePluginDescriptor implements PluginDescriptor {
|
||||
private final PluginInformation information;
|
||||
private final PluginCondition condition;
|
||||
private final Set<String> dependencies;
|
||||
private final Set<String> optionalDependencies;
|
||||
private final String url;
|
||||
private final String checksum;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #AvailablePluginDescriptor(PluginInformation, PluginCondition, Set, Set, String, String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, String url, String checksum) {
|
||||
this(information, condition, dependencies, emptySet(), url, checksum);
|
||||
}
|
||||
|
||||
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, Set<String> optionalDependencies, String url, String checksum) {
|
||||
this.information = information;
|
||||
this.condition = condition;
|
||||
this.dependencies = dependencies;
|
||||
this.optionalDependencies = optionalDependencies;
|
||||
this.url = url;
|
||||
this.checksum = checksum;
|
||||
}
|
||||
@@ -68,4 +80,9 @@ public class AvailablePluginDescriptor implements PluginDescriptor {
|
||||
public Set<String> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getOptionalDependencies() {
|
||||
return optionalDependencies;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,7 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getOptionalDependencies() {
|
||||
if (optionalDependencies == null)
|
||||
{
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
|
||||
public interface PluginDescriptor {
|
||||
|
||||
PluginInformation getInformation();
|
||||
@@ -34,4 +36,8 @@ public interface PluginDescriptor {
|
||||
|
||||
Set<String> getDependencies();
|
||||
|
||||
default Set<String> getOptionalDependencies() {
|
||||
return emptySet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export type Plugin = {
|
||||
pending: boolean;
|
||||
markedForUninstall?: boolean;
|
||||
dependencies: string[];
|
||||
optionalDependencies: string[];
|
||||
_links: Links;
|
||||
};
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"currentVersion": "Installierte Version",
|
||||
"newVersion": "Neue Version",
|
||||
"dependencyNotification": "Mit diesem Plugin werden folgende Abhängigkeiten mit installiert bzw. aktualisiert, wenn sie noch nicht in der aktuellen Version vorhanden sind!",
|
||||
"optionalDependencyNotification": "Mit diesem Plugin werden folgende optionale Abhängigkeiten mit aktualisiert, falls sie installiert sind!",
|
||||
"dependencies": "Abhängigkeiten",
|
||||
"installedNotification": "Das Plugin wurde erfolgreich installiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:",
|
||||
"updatedNotification": "Das Plugin wurde erfolgreich aktualisiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:",
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"currentVersion": "Installed version",
|
||||
"newVersion": "New version",
|
||||
"dependencyNotification": "With this plugin, the following dependencies will be installed/updated if their latest versions are not installed yet!",
|
||||
"optionalDependencyNotification": "With this plugin, the following optional dependencies will be updated if they are installed!",
|
||||
"dependencies": "Dependencies",
|
||||
"installedNotification": "Successfully installed plugin. You have to reload the page, to see ui changes:",
|
||||
"updatedNotification": "Successfully updated plugin. You have to reload the page, to see ui changes:",
|
||||
|
||||
@@ -183,6 +183,27 @@ class PluginModal extends React.Component<Props, State> {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
renderOptionalDependencies() {
|
||||
const { plugin, t } = this.props;
|
||||
|
||||
let optionalDependencies = null;
|
||||
if (plugin.optionalDependencies && plugin.optionalDependencies.length > 0) {
|
||||
optionalDependencies = (
|
||||
<div className="media">
|
||||
<Notification type="warning">
|
||||
<strong>{t("plugins.modal.optionalDependencyNotification")}</strong>
|
||||
<ul>
|
||||
{plugin.optionalDependencies.map((optionalDependency, index) => {
|
||||
return <li key={index}>{optionalDependency}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</Notification>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return optionalDependencies;
|
||||
}
|
||||
|
||||
renderNotifications = () => {
|
||||
const { t, pluginAction } = this.props;
|
||||
const { restart, error, success } = this.state;
|
||||
@@ -275,6 +296,7 @@ class PluginModal extends React.Component<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
{this.renderDependencies()}
|
||||
{this.renderOptionalDependencies()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="media">
|
||||
|
||||
@@ -53,6 +53,7 @@ public class PluginDto extends HalRepresentation {
|
||||
private Boolean core;
|
||||
private Boolean markedForUninstall;
|
||||
private Set<String> dependencies;
|
||||
private Set<String> optionalDependencies;
|
||||
|
||||
public PluginDto(Links links) {
|
||||
add(links);
|
||||
|
||||
@@ -68,6 +68,7 @@ public abstract class PluginDtoMapper {
|
||||
|
||||
private void map(PluginDto dto, Plugin plugin) {
|
||||
dto.setDependencies(plugin.getDescriptor().getDependencies());
|
||||
dto.setOptionalDependencies(plugin.getDescriptor().getOptionalDependencies());
|
||||
map(plugin.getDescriptor().getInformation(), dto);
|
||||
if (dto.getCategory() == null) {
|
||||
dto.setCategory("Miscellaneous");
|
||||
|
||||
@@ -253,21 +253,40 @@ public class DefaultPluginManager implements PluginManager {
|
||||
|
||||
private void collectPluginsToInstallOrUpdate(List<AvailablePlugin> plugins, String name) {
|
||||
if (!isInstalledOrPending(name) || isUpdatable(name)) {
|
||||
AvailablePlugin plugin = getAvailable(name).orElseThrow(() -> NotFoundException.notFound(entity(AvailablePlugin.class, name)));
|
||||
|
||||
Set<String> dependencies = plugin.getDescriptor().getDependencies();
|
||||
if (dependencies != null) {
|
||||
for (String dependency : dependencies) {
|
||||
collectPluginsToInstallOrUpdate(plugins, dependency);
|
||||
}
|
||||
}
|
||||
|
||||
plugins.add(plugin);
|
||||
collectDependentPlugins(plugins, name);
|
||||
} else {
|
||||
LOG.info("plugin {} is already installed or installation is pending, skipping installation", name);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectOptionalPluginToInstallOrUpdate(List<AvailablePlugin> plugins, String name) {
|
||||
if (isInstalledOrPending(name) && isUpdatable(name)) {
|
||||
collectDependentPlugins(plugins, name);
|
||||
} else {
|
||||
LOG.info("optional plugin {} is not installed or not updatable", name);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectDependentPlugins(List<AvailablePlugin> plugins, String name) {
|
||||
AvailablePlugin plugin = getAvailable(name).orElseThrow(() -> NotFoundException.notFound(entity(AvailablePlugin.class, name)));
|
||||
|
||||
Set<String> dependencies = plugin.getDescriptor().getDependencies();
|
||||
if (dependencies != null) {
|
||||
for (String dependency : dependencies) {
|
||||
collectPluginsToInstallOrUpdate(plugins, dependency);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> optionalDependencies = plugin.getDescriptor().getOptionalDependencies();
|
||||
if (dependencies != null) {
|
||||
for (String optionalDependency : optionalDependencies) {
|
||||
collectOptionalPluginToInstallOrUpdate(plugins, optionalDependency);
|
||||
}
|
||||
}
|
||||
|
||||
plugins.add(plugin);
|
||||
}
|
||||
|
||||
private boolean isInstalledOrPending(String name) {
|
||||
return getInstalled(name).isPresent() || getPending(name).isPresent();
|
||||
}
|
||||
|
||||
@@ -85,6 +85,9 @@ public final class PluginCenterDto implements Serializable {
|
||||
@XmlElement(name = "dependencies")
|
||||
private Set<String> dependencies;
|
||||
|
||||
@XmlElement(name = "optionalDependencies")
|
||||
private Set<String> optionalDependencies;
|
||||
|
||||
@XmlElement(name = "_links")
|
||||
private Map<String, Link> links;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public abstract class PluginCenterDtoMapper {
|
||||
for (PluginCenterDto.Plugin plugin : pluginCenterDto.getEmbedded().getPlugins()) {
|
||||
String url = plugin.getLinks().get("download").getHref();
|
||||
AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor(
|
||||
map(plugin), map(plugin.getConditions()), plugin.getDependencies(), url, plugin.getSha256sum()
|
||||
map(plugin), map(plugin.getConditions()), plugin.getDependencies(), plugin.getOptionalDependencies(), url, plugin.getSha256sum()
|
||||
);
|
||||
plugins.add(new AvailablePlugin(descriptor));
|
||||
}
|
||||
|
||||
@@ -164,6 +164,15 @@ class PluginDtoMapperTest {
|
||||
assertThat(dto.getDependencies()).containsOnly("one", "two");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendOptionalDependencies() {
|
||||
AvailablePlugin plugin = createAvailable(createPluginInformation());
|
||||
when(plugin.getDescriptor().getOptionalDependencies()).thenReturn(ImmutableSet.of("one", "two"));
|
||||
|
||||
PluginDto dto = mapper.mapAvailable(plugin);
|
||||
assertThat(dto.getOptionalDependencies()).containsOnly("one", "two");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendUninstallLink() {
|
||||
when(subject.isPermitted("plugin:write")).thenReturn(true);
|
||||
|
||||
@@ -260,6 +260,35 @@ class DefaultPluginManagerTest {
|
||||
verify(installer).install(review);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateAlreadyInstalledOptionalDependenciesWhenNewerVersionIsAvailable() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(review.getDescriptor().getOptionalDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
|
||||
AvailablePlugin mail = createAvailable("scm-mail-plugin", "1.1.0");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail));
|
||||
|
||||
InstalledPlugin installedMail = createInstalled("scm-mail-plugin", "1.0.0");
|
||||
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedMail));
|
||||
|
||||
manager.install("scm-review-plugin", false);
|
||||
|
||||
verify(installer).install(mail);
|
||||
verify(installer).install(review);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotUpdateOptionalDependenciesWhenNewerVersionIsAvailableButItIsNotInstalled() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(review.getDescriptor().getOptionalDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
|
||||
AvailablePlugin mail = createAvailable("scm-mail-plugin", "1.1.0");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail));
|
||||
|
||||
manager.install("scm-review-plugin", false);
|
||||
|
||||
verify(installer, never()).install(mail);
|
||||
verify(installer).install(review);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRollbackOnFailedInstallation() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
|
||||
@@ -67,6 +67,7 @@ class PluginCenterDtoMapperTest {
|
||||
"555000444",
|
||||
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
||||
ImmutableSet.of("scm-review-plugin"),
|
||||
ImmutableSet.of(),
|
||||
ImmutableMap.of("download", new Link("http://download.hitchhiker.com"))
|
||||
);
|
||||
|
||||
@@ -101,6 +102,7 @@ class PluginCenterDtoMapperTest {
|
||||
"12345678aa",
|
||||
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
||||
ImmutableSet.of("scm-review-plugin"),
|
||||
ImmutableSet.of(),
|
||||
ImmutableMap.of("download", new Link("http://download.hitchhiker.com/review"))
|
||||
);
|
||||
|
||||
@@ -115,6 +117,7 @@ class PluginCenterDtoMapperTest {
|
||||
"555000444",
|
||||
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
||||
ImmutableSet.of("scm-review-plugin"),
|
||||
ImmutableSet.of(),
|
||||
ImmutableMap.of("download", new Link("http://download.hitchhiker.com/hitchhiker"))
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user