Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn when operating system end of life is approaching #7913

Merged
merged 139 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from 128 commits
Commits
Show all changes
139 commits
Select commit Hold shift + click to select a range
79ec122
Ubuntu 18.04 end of life admin monitor
MarkEWaite Apr 28, 2023
3663bdd
Use a base class for common features
MarkEWaite Apr 28, 2023
0c65e6d
Better comment
MarkEWaite Apr 28, 2023
3d64308
Make the logging clearer
MarkEWaite Apr 28, 2023
8451949
Split pattern check to separate method
MarkEWaite Apr 28, 2023
191db8d
Add a message to show after end of life
MarkEWaite Apr 29, 2023
eddb6e7
Suppress most logging
MarkEWaite Apr 29, 2023
bbcf614
Minor logging improvements
MarkEWaite Apr 29, 2023
541d4d4
Use actual end of life date for Ubuntu 18.04
MarkEWaite Apr 29, 2023
60ba178
Logger not needed
MarkEWaite Apr 29, 2023
5f10769
Add Alpine 3.14 end of life admin monitor
MarkEWaite Apr 29, 2023
b9501dd
Add Alpine 3.15 end of life monitor
MarkEWaite Apr 29, 2023
bc120eb
Add Alpine 3.16 end of life monitor
MarkEWaite Apr 29, 2023
c780edb
Add Alpine 3.17 end of life monitor
MarkEWaite Apr 29, 2023
93cea3b
Spotless cleanup
MarkEWaite Apr 29, 2023
a204992
Add Fedora 37, 38, and Red Hat 7
MarkEWaite Apr 29, 2023
c1a19d4
More accurate regex to detect version
MarkEWaite Apr 29, 2023
48265f5
Add Debian 10 end of life admin monitor
MarkEWaite Apr 29, 2023
0e5774a
Add a few tests
MarkEWaite Apr 29, 2023
a172603
Add another test
MarkEWaite Apr 29, 2023
b1963e5
Fix checkstyle violations
MarkEWaite Apr 29, 2023
dd75394
Fix spotbugs warning
MarkEWaite Apr 29, 2023
52f68f1
Correct the copyright
MarkEWaite Apr 29, 2023
6153c57
Make test viable on non-Linux machines
MarkEWaite Apr 29, 2023
a1fa69d
Name the setup function more accurately
MarkEWaite Apr 29, 2023
b0b1365
Test a file with non-matching content
MarkEWaite Apr 29, 2023
eb975df
Test the Alpine end of life admin monitors
MarkEWaite Apr 29, 2023
5bde58a
Add more tests
MarkEWaite Apr 29, 2023
b5dc429
Test Fedora 37 admin monitor
MarkEWaite Apr 29, 2023
13b01fc
Add RHEL 7 test
MarkEWaite Apr 29, 2023
f3ffcaa
Announce RHEL 7 early end of support immediately
MarkEWaite Apr 29, 2023
bc21b57
Use consistent logging calls
MarkEWaite Apr 29, 2023
88181bf
Add Red Hat 8 to RHEL 7 as a test
MarkEWaite Apr 29, 2023
136020d
Use past date to report RHEL 7 end of life
MarkEWaite Apr 29, 2023
5093136
Remove Red Hat 8 from RHEL 7 end of life
MarkEWaite Apr 29, 2023
44541d3
Log more information
MarkEWaite Apr 29, 2023
0556b42
Display end of support date
MarkEWaite Apr 29, 2023
c2c82d2
Add descriptions of the admin monitors
MarkEWaite Apr 29, 2023
5ef0e79
Use a better redirect URL
MarkEWaite Apr 29, 2023
e96f6dc
Group admin monitors better by display name
MarkEWaite Apr 29, 2023
32f6d90
Remove unnused message.properties files
MarkEWaite Apr 30, 2023
85c3b96
Add description.jelly file
MarkEWaite Apr 30, 2023
ff5f651
Better description of the admin monitors
MarkEWaite Apr 30, 2023
890d636
Add endOfSupportDate getter
MarkEWaite Apr 30, 2023
e45da5f
Fix minor indentation error
MarkEWaite Apr 30, 2023
e56d308
Each monitor must be independently disabled
MarkEWaite Apr 30, 2023
79ab986
Repurpose identifier for use in disabled property
MarkEWaite Apr 30, 2023
b3b378b
Add Red Hat 8 to RHEL 7 as a test
MarkEWaite Apr 29, 2023
30b7922
Add os-release tests and data files
MarkEWaite May 1, 2023
076059d
Remove unused URL arg to constructor
MarkEWaite May 1, 2023
595b0f1
Reduce duplication in data
MarkEWaite May 1, 2023
03e0b3a
Fix inaccurate comment
MarkEWaite May 1, 2023
b29ca33
Use constant for documentation URL
MarkEWaite May 1, 2023
5f1151d
Remove RHEL 8 from RHEL 8 Admin Monitor
MarkEWaite May 1, 2023
9ed7d27
Fedora 36 security support ends 18 May 2023
MarkEWaite May 1, 2023
f92d903
Fedora 37 support ends 15 Dec 2023
MarkEWaite May 1, 2023
1753bee
Fix spelling error in description
MarkEWaite May 1, 2023
89c39fa
Read data file with UTF-8 character set
MarkEWaite May 1, 2023
1f88881
Do not use extension point name in symbol
MarkEWaite May 1, 2023
8ae1172
Use single word 'Ignore' in button, not name of monitor
MarkEWaite May 1, 2023
545315e
Use a smaller layout for monitor name
MarkEWaite May 1, 2023
64c2d71
Include operating system version in RHEL 7 check
MarkEWaite May 2, 2023
e0bafab
Merge branch 'master' into file-based-admin-monitor
MarkEWaite May 2, 2023
9ba7922
Fix regex syntax error
MarkEWaite May 3, 2023
530ffb2
Merge branch 'master' into file-based-admin-monitor
MarkEWaite May 3, 2023
672d4e8
Merge branch 'master' into file-based-admin-monitor
MarkEWaite May 18, 2023
418fd6b
First prototype using a data file
MarkEWaite May 19, 2023
2e69d15
Start the conversion to a single monitor
MarkEWaite May 19, 2023
4051345
Latest experiments with end of life data as JSON
MarkEWaite May 19, 2023
9877858
More exploring and evolving
MarkEWaite May 19, 2023
92b4b9f
Remove Alpine admin monitor
MarkEWaite May 19, 2023
2f22b75
Remove Fedora admin monitor
MarkEWaite May 19, 2023
5c909de
Remove Debian admin monitor
MarkEWaite May 19, 2023
2e64491
Remove Ubuntu admin monitor
MarkEWaite May 19, 2023
838ede2
Throw IOException rather than check for null
MarkEWaite May 19, 2023
c3efca4
Stop inheriting from EndOfLifeAdminMonitor
MarkEWaite May 19, 2023
8e2d36a
Simplify initialization logic
MarkEWaite May 19, 2023
e112f51
Add isActivated implementation
MarkEWaite May 19, 2023
450f01b
Add setDisabled test method
MarkEWaite May 19, 2023
566f5fd
Minor formatting changes
MarkEWaite May 19, 2023
8284f73
Merge branch 'master' into file-based-admin-monitor
MarkEWaite May 19, 2023
982a542
Test some additional methods
MarkEWaite May 19, 2023
684e0a8
Call super in constructor
MarkEWaite May 19, 2023
3c37ad8
Minor updates from study of upstream class
MarkEWaite May 19, 2023
a81c195
Format JSON data
MarkEWaite May 19, 2023
26578bf
Fix formatting errors
MarkEWaite May 19, 2023
c1b9f22
Fix assertion
MarkEWaite May 19, 2023
a3e7449
Better variable naming
MarkEWaite May 20, 2023
4bb9c5c
Explain the purpose of ignoreEndOfLife flag
MarkEWaite May 20, 2023
1466208
Remove unused import
MarkEWaite May 20, 2023
cd005e2
Store the pretty name, not the pattern
MarkEWaite May 20, 2023
f42d440
Merge remote-tracking branch 'upstream/master' into file-based-admin-…
MarkEWaite May 20, 2023
a5c42a7
Test pretty name with sample os-release data files
MarkEWaite May 20, 2023
ed1c5ac
Remove RedHat 7 specific monitor
MarkEWaite May 20, 2023
59c32d9
Set RHEL 8 end of life early for test
MarkEWaite May 20, 2023
c1e8da1
Ongoing debugging
MarkEWaite May 20, 2023
0939ba4
Describe the monitor
MarkEWaite May 20, 2023
ef3ca91
Honor start date
MarkEWaite May 20, 2023
9c0abbb
Add pretty name as monitor attribute
MarkEWaite May 20, 2023
8139d9d
Add end of support date
MarkEWaite May 20, 2023
23370c5
Remove earlier implementations
MarkEWaite May 20, 2023
2f37f91
Move end of life data
MarkEWaite May 20, 2023
4db9468
Rename end of support to end of life
MarkEWaite May 20, 2023
45e92bd
Improve message with after end of life date status
MarkEWaite May 20, 2023
8eb7274
Undo .gitignore change
MarkEWaite May 20, 2023
9800502
Rename effective date to end of life date
MarkEWaite May 20, 2023
edf07a4
Rename pretty name to operating system name
MarkEWaite May 20, 2023
1b3f372
More rename of prettyName
MarkEWaite May 20, 2023
48f6f13
Fix logging mistakes
MarkEWaite May 20, 2023
45e9186
Add RHEL 8
MarkEWaite May 20, 2023
a7b708f
Default start date is 6 months before end of life
MarkEWaite May 20, 2023
39dc1b4
Add operating system end of life patterns and dates
MarkEWaite May 20, 2023
8163734
Fix JSON file formatting
MarkEWaite May 20, 2023
1146192
Simplify logging code
MarkEWaite May 20, 2023
f795d90
Better logging
MarkEWaite May 20, 2023
2d5cd49
Add Alma and Rocky Linux tests and patterns
MarkEWaite May 20, 2023
7d7fd29
Add methods to help test automation
MarkEWaite May 20, 2023
f6e7cce
Remove unused methods
MarkEWaite May 20, 2023
6bf0365
Start warning RHEL 7 users immediately
MarkEWaite May 20, 2023
81477d7
Suppress spotbugs traversal_in warning - false positive
MarkEWaite May 20, 2023
88c9e37
Throw IOException on bad data file
MarkEWaite May 20, 2023
5a2d9d4
Better exception message
MarkEWaite May 20, 2023
83b4822
Correct RHEL 8 end of life date
MarkEWaite May 20, 2023
853352a
Enable isActivated test
MarkEWaite May 20, 2023
f3ae8e6
Increase test coverage
MarkEWaite May 20, 2023
135eff0
Fix spelling error
MarkEWaite May 21, 2023
4cb8994
Remove non-breaking spaces from message
MarkEWaite May 21, 2023
cfb7be5
Rename class to match symbol
MarkEWaite May 21, 2023
dae7e51
Sort end of life data alphabetically
MarkEWaite May 22, 2023
24d991a
Included OS query parameter and fragment in docs URL
MarkEWaite May 22, 2023
00634bc
Scientific Linux 8 does not exist
MarkEWaite May 22, 2023
5b53419
Merge branch 'master' into file-based-admin-monitor
MarkEWaite May 22, 2023
ab27488
Merge branch 'master' into file-based-admin-monitor
MarkEWaite May 22, 2023
ce88af8
Merge branch 'master' into file-based-admin-monitor
MarkEWaite May 23, 2023
0a35a4a
Remove fragment, only need query parameter
MarkEWaite May 23, 2023
4687a1c
Merge branch 'master' into file-based-admin-monitor
MarkEWaite May 23, 2023
98ffe88
Merge branch 'master' into file-based-admin-monitor
timja May 23, 2023
c306a32
Merge branch 'master' into file-based-admin-monitor
MarkEWaite May 24, 2023
5cd42d4
Merge branch 'master' into file-based-admin-monitor
NotMyFault May 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
* The MIT License
*
* Copyright 2023 Mark Waite.
*
* 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 jenkins.monitor;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.model.AdministrativeMonitor;
import hudson.security.Permission;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.LocalDate;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.interceptor.RequirePOST;

@Extension
@Restricted(NoExternalUse.class)
@Symbol("operatingSystemEndOfLife")
public class OperatingSystemEndOfLifeAdminMonitor extends AdministrativeMonitor {

static final Logger LOGGER = Logger.getLogger(OperatingSystemEndOfLifeAdminMonitor.class.getName());

/**
* Allow tests to disable the end of life monitor without a JenkinsRule.
*/
boolean ignoreEndOfLife = false;

private LocalDate warningsStartDate = LocalDate.now().plusYears(10);
private boolean afterEndOfLifeDate = false;
private String operatingSystemName = System.getProperty("os.name", "Unknown");
private String endOfLifeDate = "2099-12-31";

public OperatingSystemEndOfLifeAdminMonitor(String id) throws IOException {
super(id);
fillOperatingSystemList();
}

public OperatingSystemEndOfLifeAdminMonitor() throws IOException {
fillOperatingSystemList();
}

private void fillOperatingSystemList() throws IOException {
if (Jenkins.getInstanceOrNull() != null && !isEnabled()) {
/* If not enabled, do not read the data files or perform any checks */
LOGGER.log(Level.FINEST, "Operating system end of life monitor is not enabled, reading no data");
return;
}
ClassLoader cl = getClass().getClassLoader();
URL localOperatingSystemData = cl.getResource("jenkins/monitor/OperatingSystemEndOfLifeAdminMonitor/end-of-life-data.json");
String initialOperatingSystemJson = IOUtils.toString(localOperatingSystemData.openStream(), StandardCharsets.UTF_8);
readOperatingSystemList(initialOperatingSystemJson);
}

/* Package protected for testing */
void readOperatingSystemList(String initialOperatingSystemJson) throws IOException {
JSONArray systems = JSONArray.fromObject(initialOperatingSystemJson);
if (systems.isEmpty()) {
throw new IOException("Empty data set");
}
for (Object systemObj : systems) {
if (!(systemObj instanceof JSONObject)) {
throw new IOException("Wrong object type in data file");
}
JSONObject system = (JSONObject) systemObj;

if (!system.has("pattern")) {
throw new IOException("Missing pattern in definition file");
}
String pattern = system.getString("pattern");

if (!system.has("endOfLife")) {
throw new IOException("No end of life date for " + pattern);
}
LocalDate endOfLife = LocalDate.parse(system.getString("endOfLife"));

/* Start date defaults to 6 months before end of life */
LocalDate startDate = system.has("start") ? LocalDate.parse(system.getString("start")) : endOfLife.minusMonths(6);

File dataFile = getDataFile(system);

LOGGER.log(Level.FINEST, "Pattern {0} starts {1} and reaches end of life {2} from file {3}",
new Object[]{pattern, startDate, endOfLife, dataFile});

String name = readOperatingSystemName(dataFile, pattern);
if (name.isEmpty()) {
LOGGER.log(Level.FINE, "Pattern {0} did not match from file {1}",
new Object[]{pattern, dataFile});
continue;
}

if (startDate.isBefore(warningsStartDate)) {
warningsStartDate = startDate;
LOGGER.log(Level.FINE, "Warnings start date is now {0}", warningsStartDate);
}

LOGGER.log(Level.FINE, "Matched operating system {0}", name);
if (startDate.isBefore(LocalDate.now())) {
this.operatingSystemName = name;
this.endOfLifeDate = endOfLife.toString();
if (endOfLife.isBefore(LocalDate.now())) {
LOGGER.log(Level.FINE, "Operating system {0} is after end of life {1}",
new Object[]{name, endOfLife});
afterEndOfLifeDate = true;
} else {
LOGGER.log(Level.FINE, "Operating system {0} started warnings {1} and reaches end of life {2}",
new Object[]{name, startDate, endOfLife});
}
}
}
}

@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN",
justification = "File path defined in war file, not by user")
@CheckForNull
private File getDataFile(@NonNull JSONObject system) {
/* dataFile defaults to /etc/os-release */
String fileName = "/etc/os-release";
if (system.has("file")) {
fileName = system.getString("file");
}
File dataFile = new File(fileName);
return dataFile;
}

/* Package protected for testing */
@NonNull
String readOperatingSystemName(File dataFile, @NonNull String patternStr) {
if (dataFile == null || !dataFile.exists()) {
return "";
}
Pattern pattern = Pattern.compile("^PRETTY_NAME=[\"](" + patternStr + ".*)[\"]");
String name = "";
try {
List<String> lines = Files.readAllLines(dataFile.toPath());
for (String line : lines) {
Matcher matcher = pattern.matcher(line);
if (matcher.matches()) {
name = matcher.group(1);
}
}
} catch (IOException ioe) {
LOGGER.log(Level.SEVERE, "File read exception", ioe);
}
return name;
}

@NonNull
public String getOperatingSystemName() {
return operatingSystemName;
}

@NonNull
public String getEndOfLifeDate() {
return endOfLifeDate;
}

public boolean getAfterEndOfLifeDate() {
return afterEndOfLifeDate;
}

/*
* Send use to the right place depending on "yes" or "no".
*/
@Restricted(DoNotUse.class) // WebOnly
@RequirePOST
public HttpResponse doAct(@QueryParameter String no) throws IOException {
if (no != null) { // dismiss
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
disable(true);
LOGGER.log(Level.FINE, "Disabled operating system end of life monitor");
return HttpResponses.forwardToPreviousPage();
} else {
LOGGER.log(Level.FINE, "Enabled operating system end of life monitor");
return new HttpRedirect("https://www.jenkins.io/redirect/operating-system-end-of-life");
}
}

@Override
public boolean isActivated() {
if (ignoreEndOfLife) {
LOGGER.log(Level.FINE, "Not activated because ignoring end of life monitor");
return false;
}
if (LocalDate.now().isBefore(warningsStartDate)) {
LOGGER.log(Level.FINE, "Not activated because it is before the start date {0}", warningsStartDate);
return false;
}
LOGGER.log(Level.FINEST, "Activated because it is after the warnings start date {0}", warningsStartDate);
return true;
}

@Override
public Permission getRequiredPermission() {
return Jenkins.SYSTEM_READ;
}

@Override
public String getDisplayName() {
return "Operating system end of life monitor";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:st="jelly:stapler">
${%blurb}
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
blurb=Warns administrators when the controller operating system is approaching end of life and will be unsupported
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
[
{
"pattern": "AlmaLinux.* 8",
"endOfLife": "2029-03-31"
},
{
"pattern": "Alpine Linux v3.14",
"endOfLife": "2023-05-01"
},
{
"pattern": "Alpine Linux v3.15",
"endOfLife": "2023-11-01"
},
{
"pattern": "Alpine Linux v3.16",
"endOfLife": "2024-05-01"
},
{
"pattern": "Alpine Linux v3.17",
"endOfLife": "2024-11-01"
},
{
"pattern": "Alpine Linux v3.18",
"endOfLife": "2025-05-01"
},
{
"pattern": "CentOS Linux.* 7",
"start": "2023-05-01",
"endOfLife": "2023-11-16"
},
{
"pattern": "Debian.* 10",
"endOfLife": "2024-06-30"
},
{
"pattern": "Debian.* 11",
"endOfLife": "2026-06-30"
},
{
"pattern": "Debian.* 12",
"endOfLife": "2028-06-30"
},
{
"pattern": "Fedora.* 36",
"endOfLife": "2023-05-18"
},
{
"pattern": "Fedora.* 37",
"endOfLife": "2023-12-15"
},
{
"pattern": "Fedora.* 38",
"endOfLife": "2023-05-18"
},
{
"pattern": "Oracle Linux.* 7",
"start": "2023-05-01",
"endOfLife": "2023-11-16"
},
{
"pattern": "Red Hat Enterprise Linux.* 7",
"start": "2023-05-01",
"endOfLife": "2023-11-16"
},
{
"pattern": "Scientific Linux.* 7",
"start": "2023-05-01",
"endOfLife": "2023-11-16"
},
{
"pattern": "CentOS Linux.* 8",
"endOfLife": "2029-03-31"
},
{
"pattern": "Oracle Linux.* 8",
"endOfLife": "2029-03-31"
},
{
"pattern": "Red Hat Enterprise Linux.* 8",
"endOfLife": "2029-03-31"
},
{
"pattern": "Rocky Linux.* 8",
"endOfLife": "2029-03-31"
},
{
"pattern": "Scientific Linux.* 8",
"endOfLife": "2029-03-31"
},
{
"pattern": "Ubuntu.* 18.04",
"endOfLife": "2023-05-31"
},
{
"pattern": "Ubuntu.* 20.04",
"endOfLife": "2025-04-02"
},
{
"pattern": "Ubuntu.* 22.04",
"endOfLife": "2027-04-01"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!--
The MIT License

Copyright (c) 2021 Tim Jacomb.
Copyright (c) 2023 Mark Waite.

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.
-->

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:f="/lib/form">
<div class="alert alert-danger">
<form id="${it.id}" method="post" action="${rootURL}/${it.url}/act" name="${it.id}">
<f:submit name="yes" value="${%More Info}"/>
<l:isAdmin>
<f:submit value="${%Ignore}" name="no"/>
</l:isAdmin>
</form>
<h2 class="h3">${it.displayName}</h2>
<j:choose>
<j:when test="${it.afterEndOfLifeDate}">
${%Past_End_of_Life(it.operatingSystemName, it.endOfLifeDate)}
</j:when>
<j:otherwise>
${%Upcoming_End_of_Life(it.operatingSystemName, it.endOfLifeDate)}
</j:otherwise>
</j:choose>
</div>
</j:jelly>
Loading