Skip to content

Commit

Permalink
Merge pull request #7512 from uyuni-project/master-update-notifications
Browse files Browse the repository at this point in the history
Notify users when updates are available to the Uyuni server
  • Loading branch information
raulillo82 authored Sep 18, 2023
2 parents 90ab195 + 7e095ce commit 89153ff
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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");
}
}

Expand All @@ -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();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ public enum NotificationType {
PaygAuthenticationUpdateFailed,
EndOfLifePeriod,
SubscriptionWarning,
UpdateAvailable,
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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 " +
"<a href=\"https://www.uyuni-project.org/pages/stable-version.html\">release notes<a>.",
notification.getDetails());
}
else {
assertEquals("A new update for SUSE Manager is now available. For further details, please refer to the " +
"<a href=\"https://www.suse.com/releasenotes/x86_64/SUSE-MANAGER/4.3/index.html\">release " +
"notes<a>.", 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9424,6 +9424,12 @@ For a detailed analysis, please refer to the log files.</source>
<trans-unit id="notification.subscriptionwarning.detail">
<source>You have subscriptions that are expiring soon or already expired. Please check the &lt;a href="/rhn/manager/subscription-matching"&gt;Subscription Matching&lt;/a&gt; page.</source>
</trans-unit>
<trans-unit id="notification.updateavailable.summary">
<source>Updates are available.</source>
</trans-unit>
<trans-unit id="notification.updateavailable.detail">
<source>A new update for @@PRODUCT_NAME@@ is now available. For further details, please refer to the &lt;a href="{0}"&gt;release notes&lt;a&gt;.</source>
</trans-unit>
<trans-unit id="notification.endoflife.expired.summary">
<source>@@PRODUCT_NAME@@ {0} has reached end of life</source>
</trans-unit>
Expand Down
12 changes: 12 additions & 0 deletions java/code/src/com/redhat/rhn/taskomatic/task/DailySummary.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -85,6 +86,7 @@ public String getConfigNamespace() {
@Override
public void execute(JobExecutionContext ctxIn) {

processUpdateAvailableNotification();
processEndOfLifeNotification();
processSubscriptionWarningNotification();

Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion java/conf/rhn_java.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions java/spacewalk-java.changes.mseidl.update-notification
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Shows a notification when an update for Uyuni is available
4 changes: 4 additions & 0 deletions web/html/src/manager/notifications/notification-messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ const _MESSAGE_TYPE = {
id: "SubscriptionWarning",
text: t("Subscription Warning"),
},
UpdateAvailable: {
id: "UpdateAvailable",
text: t("Update Notification"),
},
};

function reloadData(dataUrlSlice: string) {
Expand Down
1 change: 1 addition & 0 deletions web/spacewalk-web.changes.mseidl.update-notification
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Shows a notification when an update for Uyuni is available

0 comments on commit 89153ff

Please sign in to comment.