diff --git a/java/code/src/com/redhat/rhn/domain/notification/NotificationMessage.java b/java/code/src/com/redhat/rhn/domain/notification/NotificationMessage.java
index 5466b26f94c3..e4398979a1d1 100644
--- a/java/code/src/com/redhat/rhn/domain/notification/NotificationMessage.java
+++ b/java/code/src/com/redhat/rhn/domain/notification/NotificationMessage.java
@@ -25,6 +25,7 @@
import com.redhat.rhn.domain.notification.types.PaygAuthenticationUpdateFailed;
import com.redhat.rhn.domain.notification.types.StateApplyFailed;
import com.redhat.rhn.domain.notification.types.SubscriptionWarning;
+import com.redhat.rhn.domain.notification.types.UpdateAvailable;
import com.google.gson.Gson;
@@ -134,7 +135,9 @@ public NotificationData getNotificationData() {
return new Gson().fromJson(getData(), EndOfLifePeriod.class);
case SubscriptionWarning:
return new Gson().fromJson(getData(), SubscriptionWarning.class);
- default: throw new RuntimeException("should not happen!");
+ case UpdateAvailable:
+ return new Gson().fromJson(getData(), UpdateAvailable.class);
+ default: throw new RuntimeException("Notification type not found");
}
}
@@ -153,6 +156,7 @@ public String getTypeAsString() {
case PaygAuthenticationUpdateFailed: return "PAYG refresh failed";
case EndOfLifePeriod: return "End of Life Period";
case SubscriptionWarning: return "Subscription Warning";
+ case UpdateAvailable: return "Updates are Available";
default: return getType().name();
}
}
diff --git a/java/code/src/com/redhat/rhn/domain/notification/types/NotificationType.java b/java/code/src/com/redhat/rhn/domain/notification/types/NotificationType.java
index cdcb45018608..99ca32a3548d 100644
--- a/java/code/src/com/redhat/rhn/domain/notification/types/NotificationType.java
+++ b/java/code/src/com/redhat/rhn/domain/notification/types/NotificationType.java
@@ -26,4 +26,5 @@ public enum NotificationType {
PaygAuthenticationUpdateFailed,
EndOfLifePeriod,
SubscriptionWarning,
+ UpdateAvailable,
}
diff --git a/java/code/src/com/redhat/rhn/domain/notification/types/UpdateAvailable.java b/java/code/src/com/redhat/rhn/domain/notification/types/UpdateAvailable.java
new file mode 100644
index 000000000000..acb3ed4857b9
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/domain/notification/types/UpdateAvailable.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2023 SUSE LLC
+ *
+ * This software is licensed to you under the GNU General Public License,
+ * version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ * implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ * along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * Red Hat trademarks are not licensed under GPLv2. No permission is
+ * granted to use or replicate Red Hat trademarks that are incorporated
+ * in this software or its documentation.
+ */
+package com.redhat.rhn.domain.notification.types;
+
+import com.redhat.rhn.common.conf.ConfigDefaults;
+import com.redhat.rhn.common.localization.LocalizationService;
+import com.redhat.rhn.domain.notification.NotificationMessage;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+
+/**
+ * Notification data for an update being available for the server.
+ */
+public class UpdateAvailable implements NotificationData {
+
+ private static final LocalizationService LOCALIZATION_SERVICE = LocalizationService.getInstance();
+ private static final Logger LOG = LogManager.getLogger(UpdateAvailable.class);
+ private static final String UYUNI_PATCH_REPO = "systemsmanagement_Uyuni_Stable_Patches";
+ private static final String UYUNI_UPDATE_REPO = "systemsmanagement_Uyuni_Stable";
+
+ private final boolean mgr = !ConfigDefaults.get().isUyuni();
+ private final String version = StringUtils.substringBeforeLast(ConfigDefaults.get().getProductVersion(), ".");
+ private final Runtime runtime;
+
+ /**
+ * Constructor allowing to pass the runtime as argument.
+ *
+ * @param runtimeIn runtime object for command execution
+ */
+ public UpdateAvailable(Runtime runtimeIn) {
+ this.runtime = runtimeIn;
+ }
+
+ /**
+ * returns true if there are updates available.
+ *
+ * @return boolean
+ **/
+ public boolean updateAvailable() {
+ boolean hasUpdates = false;
+ String repo = mgr ? "SLE-Module-SUSE-Manager-Server-" + version + "-Updates" : UYUNI_PATCH_REPO;
+
+ try {
+ Process patchProc = runtime.exec(new String[]{"/bin/bash", "-c",
+ "LC_ALL=C zypper lp -r " + repo + " | grep 'applicable patch'"});
+ patchProc.waitFor();
+ // 0 here means there are patches
+ hasUpdates = (0 == patchProc.exitValue());
+ if (!hasUpdates && !mgr) {
+ // Check for updates on uyuni when there are no patches
+ Process updateProc = runtime.exec(new String[]{"/bin/bash", "-c",
+ "LC_ALL=C zypper lu -r " + UYUNI_UPDATE_REPO + " | grep 'Available Version'"});
+ updateProc.waitFor();
+ hasUpdates = (0 == updateProc.exitValue());
+ }
+ }
+ catch (IOException e) {
+ LOG.warn("Unable to check for updates", e);
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LOG.warn("Wait for update check was interrupted", e);
+ }
+ return hasUpdates;
+ }
+
+ @Override
+ public NotificationMessage.NotificationMessageSeverity getSeverity() {
+ return NotificationMessage.NotificationMessageSeverity.warning;
+ }
+
+ @Override
+ public NotificationType getType() {
+ return NotificationType.UpdateAvailable;
+ }
+
+ @Override
+ public String getSummary() {
+ return LOCALIZATION_SERVICE.getMessage("notification.updateavailable.summary");
+ }
+
+ @Override
+ public String getDetails() {
+ String url = mgr ?
+ "https://www.suse.com/releasenotes/x86_64/SUSE-MANAGER/" + version + "/index.html" :
+ "https://www.uyuni-project.org/pages/stable-version.html";
+ return LOCALIZATION_SERVICE.getMessage("notification.updateavailable.detail", url);
+ }
+}
diff --git a/java/code/src/com/redhat/rhn/domain/notification/types/test/UpdateAvailableTest.java b/java/code/src/com/redhat/rhn/domain/notification/types/test/UpdateAvailableTest.java
new file mode 100644
index 000000000000..d5c297799380
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/domain/notification/types/test/UpdateAvailableTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2023 SUSE LLC
+ *
+ * This software is licensed to you under the GNU General Public License,
+ * version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ * implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ * along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * Red Hat trademarks are not licensed under GPLv2. No permission is
+ * granted to use or replicate Red Hat trademarks that are incorporated
+ * in this software or its documentation.
+ */
+package com.redhat.rhn.domain.notification.types.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.redhat.rhn.common.conf.ConfigDefaults;
+import com.redhat.rhn.domain.notification.NotificationMessage;
+import com.redhat.rhn.domain.notification.types.NotificationType;
+import com.redhat.rhn.domain.notification.types.UpdateAvailable;
+import com.redhat.rhn.testing.MockObjectTestCase;
+
+import org.jmock.Expectations;
+import org.jmock.imposters.ByteBuddyClassImposteriser;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class UpdateAvailableTest extends MockObjectTestCase {
+
+ private Runtime runtimeMock;
+ private Process processMock;
+
+ @BeforeEach
+ public void setUp() {
+ setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
+ runtimeMock = mock(Runtime.class);
+ processMock = mock(Process.class);
+ }
+
+ @Test
+ public void testPropertiesAndStrings() {
+ UpdateAvailable notification = new UpdateAvailable(runtimeMock);
+ assertEquals(NotificationType.UpdateAvailable, notification.getType());
+ assertEquals(NotificationMessage.NotificationMessageSeverity.warning, notification.getSeverity());
+ assertEquals("Updates are available.", notification.getSummary());
+ if (ConfigDefaults.get().isUyuni()) {
+ assertEquals("A new update for Uyuni is now available. For further details, please refer to the " +
+ "release notes.",
+ notification.getDetails());
+ }
+ else {
+ assertEquals("A new update for SUSE Manager is now available. For further details, please refer to the " +
+ "release " +
+ "notes.", notification.getDetails());
+ }
+ }
+
+ @Test
+ public void testUpdatesAvailable() throws Exception {
+ // Return 0 on all invocations of exec() -> an update is available
+ checking(new Expectations() {{
+ allowing(runtimeMock).exec(with(any(String[].class)));
+ will(returnValue(processMock));
+ allowing(processMock).waitFor();
+ allowing(processMock).exitValue();
+ will(returnValue(0));
+ }});
+
+ UpdateAvailable notification = new UpdateAvailable(runtimeMock);
+ assertTrue(notification.updateAvailable());
+ }
+
+ @Test
+ public void testNoUpdatesAvailable() throws Exception {
+ // Return 1 on all invocations of exec() -> no update is available
+ checking(new Expectations() {{
+ allowing(runtimeMock).exec(with(any(String[].class)));
+ will(returnValue(processMock));
+ allowing(processMock).waitFor();
+ allowing(processMock).exitValue();
+ will(returnValue(1));
+ }});
+
+ UpdateAvailable notification = new UpdateAvailable(runtimeMock);
+ assertFalse(notification.updateAvailable());
+ }
+}
diff --git a/java/code/src/com/redhat/rhn/frontend/strings/java/StringResource_en_US.xml b/java/code/src/com/redhat/rhn/frontend/strings/java/StringResource_en_US.xml
index 9c220e7a1657..975b639f13ff 100644
--- a/java/code/src/com/redhat/rhn/frontend/strings/java/StringResource_en_US.xml
+++ b/java/code/src/com/redhat/rhn/frontend/strings/java/StringResource_en_US.xml
@@ -9424,6 +9424,12 @@ For a detailed analysis, please refer to the log files.
+
+
+
+
+
+
diff --git a/java/code/src/com/redhat/rhn/taskomatic/task/DailySummary.java b/java/code/src/com/redhat/rhn/taskomatic/task/DailySummary.java
index 9fa161e4847c..5e970a7a2674 100644
--- a/java/code/src/com/redhat/rhn/taskomatic/task/DailySummary.java
+++ b/java/code/src/com/redhat/rhn/taskomatic/task/DailySummary.java
@@ -24,6 +24,7 @@
import com.redhat.rhn.domain.notification.UserNotificationFactory;
import com.redhat.rhn.domain.notification.types.EndOfLifePeriod;
import com.redhat.rhn.domain.notification.types.SubscriptionWarning;
+import com.redhat.rhn.domain.notification.types.UpdateAvailable;
import com.redhat.rhn.domain.org.OrgFactory;
import com.redhat.rhn.domain.role.RoleFactory;
import com.redhat.rhn.frontend.dto.ActionMessage;
@@ -85,6 +86,7 @@ public String getConfigNamespace() {
@Override
public void execute(JobExecutionContext ctxIn) {
+ processUpdateAvailableNotification();
processEndOfLifeNotification();
processSubscriptionWarningNotification();
@@ -139,6 +141,16 @@ private void processSubscriptionWarningNotification() {
}
}
+ private void processUpdateAvailableNotification() {
+ UpdateAvailable uan = new UpdateAvailable(Runtime.getRuntime());
+ if (uan.updateAvailable()) {
+ NotificationMessage notificationMessage =
+ UserNotificationFactory.createNotificationMessage(uan);
+ UserNotificationFactory.storeNotificationMessageFor(notificationMessage,
+ Collections.singleton(RoleFactory.SAT_ADMIN), Optional.empty());
+ }
+ }
+
private void processEmails() {
SelectMode m = ModeFactory.getMode(TaskConstants.MODE_NAME,
TaskConstants.TASK_QUERY_DAILY_SUMMARY_QUEUE);
diff --git a/java/conf/rhn_java.conf b/java/conf/rhn_java.conf
index 73e991186233..0ff252b7690f 100644
--- a/java/conf/rhn_java.conf
+++ b/java/conf/rhn_java.conf
@@ -217,7 +217,7 @@ java.kiwi_os_image_building_enabled = true
java.notifications_lifetime = 30
# Configure the disablement of notification messages by type - example disabling all notification types
-#java.notifications_type_disabled = OnboardingFailed,ChannelSyncFailed,ChannelSyncFinished,CreateBootstrapRepoFailed,StateApplyFailed
+#java.notifications_type_disabled = OnboardingFailed,ChannelSyncFailed,ChannelSyncFinished,CreateBootstrapRepoFailed,StateApplyFailed,UpdateAvailable,SubscriptionWarning
java.notifications_type_disabled = ChannelSyncFinished
# Maximal number of parallel connections to refresh from SCC
diff --git a/java/spacewalk-java.changes.mseidl.update-notification b/java/spacewalk-java.changes.mseidl.update-notification
new file mode 100644
index 000000000000..79e5cd4614bc
--- /dev/null
+++ b/java/spacewalk-java.changes.mseidl.update-notification
@@ -0,0 +1 @@
+- Shows a notification when an update for Uyuni is available
diff --git a/web/html/src/manager/notifications/notification-messages.tsx b/web/html/src/manager/notifications/notification-messages.tsx
index f0689b6f438c..bb45aa9b54da 100644
--- a/web/html/src/manager/notifications/notification-messages.tsx
+++ b/web/html/src/manager/notifications/notification-messages.tsx
@@ -56,6 +56,10 @@ const _MESSAGE_TYPE = {
id: "SubscriptionWarning",
text: t("Subscription Warning"),
},
+ UpdateAvailable: {
+ id: "UpdateAvailable",
+ text: t("Update Notification"),
+ },
};
function reloadData(dataUrlSlice: string) {
diff --git a/web/spacewalk-web.changes.mseidl.update-notification b/web/spacewalk-web.changes.mseidl.update-notification
new file mode 100644
index 000000000000..79e5cd4614bc
--- /dev/null
+++ b/web/spacewalk-web.changes.mseidl.update-notification
@@ -0,0 +1 @@
+- Shows a notification when an update for Uyuni is available