diff --git a/CHANGELOG.md b/CHANGELOG.md index b2595d3e8e..daee9aa86b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added - Extension point to add links to the repository cards from plug ins ([#1041](https://github.com/scm-manager/scm-manager/pull/1041)) +- Libc based restart strategy for posix operating systems ([#1079](https://github.com/scm-manager/scm-manager/pull/1079)) +- Simple restart strategy with System.exit ([#1079](https://github.com/scm-manager/scm-manager/pull/1079)) +- Notification if restart is not supported on the underlying platform ([#1079](https://github.com/scm-manager/scm-manager/pull/1079)) ### Changed - Update resteasy to version 4.5.2.Final +- Update shiro to version 1.5.2 - Use browser built-in EventSource for apiClient subscriptions - Changeover to MIT license ([#1066](https://github.com/scm-manager/scm-manager/pull/1066)) ### Removed - EventSource Polyfill +- ClassLoader based restart logic ([#1079](https://github.com/scm-manager/scm-manager/pull/1079)) ### Fixed - Build on windows ([#1048](https://github.com/scm-manager/scm-manager/issues/1048), [#1049](https://github.com/scm-manager/scm-manager/issues/1049), [#1056](https://github.com/scm-manager/scm-manager/pull/1056)) diff --git a/pom.xml b/pom.xml index c3f6cb139f..2054116a65 100644 --- a/pom.xml +++ b/pom.xml @@ -363,6 +363,13 @@ test + + junit + junit + 4.13 + test + + org.hamcrest hamcrest-core @@ -919,7 +926,7 @@ 1.2.0 - 1.5.1 + 1.5.2 5.6.1.202002131546-r-scm1 diff --git a/scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java b/scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java index 0315103d22..2485e78de4 100644 --- a/scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java +++ b/scm-core/src/main/java/sonia/scm/lifecycle/RestartEvent.java @@ -29,63 +29,37 @@ package sonia.scm.lifecycle; import sonia.scm.event.Event; /** - * This event can be used to force a restart of the webapp context. The restart - * event is useful during plugin development, because we don't have to restart - * the whole server, to see our changes. The restart event could also be used - * to install or upgrade plugins. - * - * But the restart event should be used carefully, because the whole context - * will be restarted and that process could take some time. - * + * This event indicates a forced restart of scm-manager. * @author Sebastian Sdorra * @since 2.0.0 */ @Event -public class RestartEvent -{ +public class RestartEvent { - /** - * Constructs ... - * - * - * @param cause - * @param reason - */ - public RestartEvent(Class cause, String reason) - { + private final Class cause; + private final String reason; + + RestartEvent(Class cause, String reason) { this.cause = cause; this.reason = reason; } - //~--- get methods ---------------------------------------------------------- - /** * The class which has fired the restart event. * - * * @return class which has fired the restart event */ - public Class getCause() - { + public Class getCause() { return cause; } /** * Returns the reason for the restart. * - * * @return reason for restart */ - public String getReason() - { + public String getReason() { return reason; } - //~--- fields --------------------------------------------------------------- - - /** cause of restart */ - private final Class cause; - - /** reason for restart */ - private final String reason; } diff --git a/scm-core/src/main/java/sonia/scm/lifecycle/RestartNotSupportedException.java b/scm-core/src/main/java/sonia/scm/lifecycle/RestartNotSupportedException.java new file mode 100644 index 0000000000..7b560f7222 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/lifecycle/RestartNotSupportedException.java @@ -0,0 +1,37 @@ +/* + * 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.lifecycle; + +/** + * Exception is thrown if a restart is not supported or a restart strategy is misconfigured. + */ +public class RestartNotSupportedException extends RuntimeException { + RestartNotSupportedException(String message) { + super(message); + } + + RestartNotSupportedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/scm-core/src/main/java/sonia/scm/lifecycle/Restarter.java b/scm-core/src/main/java/sonia/scm/lifecycle/Restarter.java new file mode 100644 index 0000000000..12f9279c80 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/lifecycle/Restarter.java @@ -0,0 +1,49 @@ +/* + * 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.lifecycle; + +/** + * {@link Restarter} is able to restart scm-manager. + * + * @since 2.0.0 + */ +public interface Restarter { + + /** + * Return {@code true} if restarting scm-manager is supported. + * + * @return {@code true} if restart is supported + */ + boolean isSupported(); + + /** + * Issues a restart. The method will fire a {@link RestartEvent} to notify the system about the upcoming restart. + * If restarting is not supported by the underlying platform a {@link RestartNotSupportedException} is thrown. + * + * @param cause cause of the restart. This should be the class which calls this method. + * @param reason reason for the required restart. + * @throws RestartNotSupportedException if restarting is not supported by the underlying platform. + */ + void restart(Class cause, String reason); +} diff --git a/scm-test/pom.xml b/scm-test/pom.xml index 05d773a6f8..a24e048f55 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -38,7 +38,7 @@ scm-test 2.0.0-SNAPSHOT scm-test - + @@ -53,6 +53,12 @@ 2.0.0-SNAPSHOT + + junit + junit + compile + + com.github.sdorra shiro-unit @@ -88,17 +94,17 @@ - + - + - + maven.tmatesoft.com tmatesoft release repository https://maven.tmatesoft.com/content/repositories/releases - + diff --git a/scm-test/src/main/java/sonia/scm/lifecycle/RestartEventFactory.java b/scm-test/src/main/java/sonia/scm/lifecycle/RestartEventFactory.java new file mode 100644 index 0000000000..d58897dd40 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/lifecycle/RestartEventFactory.java @@ -0,0 +1,38 @@ +/* + * 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.lifecycle; + +/** + * Creates restart events for testing. + * This is required, because the constructor of {@link RestartEvent} is package private. + */ +public final class RestartEventFactory { + + private RestartEventFactory(){} + + public static RestartEvent create(Class cause, String reason) { + return new RestartEvent(cause, reason); + } + +} diff --git a/scm-ui/ui-webapp/public/locales/de/admin.json b/scm-ui/ui-webapp/public/locales/de/admin.json index e65a10eb85..62ba36bd69 100644 --- a/scm-ui/ui-webapp/public/locales/de/admin.json +++ b/scm-ui/ui-webapp/public/locales/de/admin.json @@ -30,6 +30,7 @@ "installedNavLink": "Installiert", "availableNavLink": "Verfügbar" }, + "showPending": "Änderungen anzeigen", "executePending": "Änderungen ausführen", "outdatedPlugins": "{{count}} veraltetes Plugin", "outdatedPlugins_plural": "{{count}} veraltete Plugins", @@ -55,6 +56,7 @@ "executeAndRestart": "Ausführen und Neustarten", "updateAll": "Alle Plugins aktualisieren", "abort": "Abbrechen", + "close": "Schließen", "author": "Autor", "version": "Version", "currentVersion": "Installierte Version", @@ -66,10 +68,12 @@ "uninstalledNotification": "Das Plugin wurde erfolgreich installiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:", "executedChangesNotification": "Die Plugin Änderungen wurden erfolgreich durchgeführt. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:", "reload": "jetzt neu laden", - "restartNotification": "Der SCM-Manager Kontext sollte nur neu gestartet werden, wenn aktuell niemand damit arbeitet.", - "executePending": "Die folgenden Plugin-Änderungen werden ausgeführt. Anschließend wird der SCM-Manager Kontext neu gestartet.", + "restartNotification": "Der SCM-Manager sollte nur neu gestartet werden, wenn aktuell niemand damit arbeitet.", + "executePending": "Die folgenden Plugin-Änderungen werden ausgeführt. Anschließend wird SCM-Manager neu gestartet.", "cancelPending": "Die folgenden Plugin-Änderungen werden abgebrochen und zurückgesetzt.", - "updateAllInfo": "Die folgenden Plugins werden aktualisiert. Die Änderungen werden nach dem nächsten Neustart wirksam." + "updateAllInfo": "Die folgenden Plugins werden aktualisiert. Die Änderungen werden nach dem nächsten Neustart wirksam.", + "manualRestartRequired": "Nachdem die Plugin-Änderung durchgeführt wurde, muss SCM-Manager neu gestartet werden.", + "showPending": "Um die folgenden Plugin-Änderungen auszuführen, muss SCM-Manager neu gestartet werden." } }, "repositoryRole": { diff --git a/scm-ui/ui-webapp/public/locales/en/admin.json b/scm-ui/ui-webapp/public/locales/en/admin.json index 5e7e44dcd2..7afad7829e 100644 --- a/scm-ui/ui-webapp/public/locales/en/admin.json +++ b/scm-ui/ui-webapp/public/locales/en/admin.json @@ -30,6 +30,7 @@ "installedNavLink": "Installed", "availableNavLink": "Available" }, + "showPending": "Show changes", "executePending": "Execute changes", "outdatedPlugins": "{{count}} outdated plugin", "outdatedPlugins_plural": "{{count}} outdated plugins", @@ -55,6 +56,7 @@ "executeAndRestart": "Execute and Restart", "updateAll": "Update all plugins", "abort": "Abort", + "close": "Close", "author": "Author", "version": "Version", "currentVersion": "Installed version", @@ -66,10 +68,12 @@ "uninstalledNotification": "Successfully uninstalled plugin. You have to reload the page, to see ui changes:", "executedChangesNotification": "Successfully executed plugin changes. You have to reload the page, to see ui changes:", "reload": "reload now", - "restartNotification": "You should only restart the scm-manager context if no one else is currently working with it.", - "executePending": "The following plugin changes will be executed and after that the scm-manager context will be restarted.", + "restartNotification": "You should only restart SCM-Manager if no one else is currently working with it.", + "executePending": "The following plugin changes will be executed and after that the SCM-Manager will be restarted.", "cancelPending": "The following plugin changes will be canceled.", - "updateAllInfo": "The following plugin changes will be executed. You need to restart the scm-manager to make these changes effective." + "updateAllInfo": "The following plugin changes will be executed. You need to restart the SCM-Manager to make these changes effective.", + "manualRestartRequired": "After the plugin change has been made, SCM-Manager must be restarted.", + "showPending": "To execute the following plugin changes, SCM-Manager must be restarted." } }, "repositoryRole": { diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/ExecutePendingModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/ExecutePendingModal.tsx index c0b35a4027..fbec769aaa 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/ExecutePendingModal.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/ExecutePendingModal.tsx @@ -27,6 +27,7 @@ import { PendingPlugins } from "@scm-manager/ui-types"; import { WithTranslation, withTranslation } from "react-i18next"; import waitForRestart from "./waitForRestart"; import SuccessNotification from "./SuccessNotification"; +import PendingPluginsQueue from "./PendingPluginsQueue"; type Props = WithTranslation & { onClose: () => void; @@ -85,70 +86,14 @@ class ExecutePendingModal extends React.Component { }); }; - renderInstallQueue = () => { - const { pendingPlugins, t } = this.props; - return ( - <> - {pendingPlugins._embedded && pendingPlugins._embedded.new.length > 0 && ( - <> - {t("plugins.modal.installQueue")} - - - )} - - ); - }; - - renderUpdateQueue = () => { - const { pendingPlugins, t } = this.props; - return ( - <> - {pendingPlugins._embedded && pendingPlugins._embedded.update.length > 0 && ( - <> - {t("plugins.modal.updateQueue")} - - - )} - - ); - }; - - renderUninstallQueue = () => { - const { pendingPlugins, t } = this.props; - return ( - <> - {pendingPlugins._embedded && pendingPlugins._embedded.uninstall.length > 0 && ( - <> - {t("plugins.modal.uninstallQueue")} - - - )} - - ); - }; - renderBody = () => { - const { t } = this.props; + const { pendingPlugins, t } = this.props; return ( <>

{t("plugins.modal.executePending")}

- {this.renderInstallQueue()} - {this.renderUpdateQueue()} - {this.renderUninstallQueue()} +
{this.renderNotifications()}
diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PendingPluginsQueue.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PendingPluginsQueue.tsx new file mode 100644 index 0000000000..f8fd298674 --- /dev/null +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PendingPluginsQueue.tsx @@ -0,0 +1,66 @@ +/* + * 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. + */ + +import React, { FC } from "react"; +import { PendingPlugins } from "@scm-manager/ui-types"; +import { useTranslation } from "react-i18next"; + +type Props = { + pendingPlugins: PendingPlugins; +}; + +type SectionProps = Props & { + type: string; + label: string; +}; + +const Section: FC = ({ pendingPlugins, type, label }) => { + const plugins = pendingPlugins?._embedded[type]; + if (!plugins || plugins.length === 0) { + return null; + } + return ( + <> + {label} +
    + {plugins.map(plugin => ( +
  • {plugin.name}
  • + ))} +
+ + ); +}; + +const PendingPluginsQueue: FC = ({ pendingPlugins }) => { + const [t] = useTranslation("admin"); + return ( + <> +
+
+
+ + ); +}; + +export default PendingPluginsQueue; diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx index 7c1d12a6b2..1074588e59 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.tsx @@ -214,8 +214,25 @@ class PluginModal extends React.Component { }); }; - render() { + createRestartSectionContent = () => { const { restart } = this.state; + const { plugin, pluginAction, t } = this.props; + + if (plugin._links[pluginAction + "WithRestart"]) { + return ( + + ); + } else { + return {t("plugins.modal.manualRestartRequired")}; + } + }; + + render() { const { plugin, pluginAction, onClose, t } = this.props; const body = ( @@ -262,12 +279,7 @@ class PluginModal extends React.Component {
- + {this.createRestartSectionContent()}
{this.renderNotifications()} diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/ShowPendingModal.tsx b/scm-ui/ui-webapp/src/admin/plugins/components/ShowPendingModal.tsx new file mode 100644 index 0000000000..d28b51f927 --- /dev/null +++ b/scm-ui/ui-webapp/src/admin/plugins/components/ShowPendingModal.tsx @@ -0,0 +1,70 @@ +/* + * 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. + */ +import React, { FC } from "react"; +import { Button, Modal, Notification } from "@scm-manager/ui-components"; +import { PendingPlugins } from "@scm-manager/ui-types"; +import { useTranslation } from "react-i18next"; +import PendingPluginsQueue from "./PendingPluginsQueue"; + + +type ModalBodyProps = { + pendingPlugins: PendingPlugins; +}; + +const ModalBody: FC = ({ pendingPlugins }) => { + const [t] = useTranslation("admin"); + return ( + <> +
+
+

{t("plugins.modal.showPending")}

+ +
+
+
+ {t("plugins.modal.restartNotification")} +
+ + ); +}; + +type Props = { + onClose: () => void; + pendingPlugins: PendingPlugins; +}; + +const ShowPendingModal: FC = ({ pendingPlugins, onClose }) => { + const [t] = useTranslation("admin"); + return ( + } + footer={