2 | Eine Liste der Wartungsfenster für diesen Agenten.
3 | Ein Wartungsfenster wird nach Ablauf automatisch aus der Liste entfernt.
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help/help-keepUpWhenActive.html:
--------------------------------------------------------------------------------
1 | If checked and this agent is scheduled to be taken offline but there are builds in progress, Jenkins will wait for those builds to complete before taking this agent offline.
2 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceAction/index.properties:
--------------------------------------------------------------------------------
1 | missingPermission=Maintenance Availability is currently not enabled for this agent. Computer.CONFIGURE permission is required to enable it.
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/Messages_de.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/agent-maintenance-plugin/main/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/Messages_de.properties
--------------------------------------------------------------------------------
/src/main/webapp/help/help-disable.html:
--------------------------------------------------------------------------------
1 |
2 | By clicking the Disable button all existing maintenance windows for this agent will be deleted.
3 | The currently defined Regular Availability will become the agents new availability.
4 |
2 | By clicking the Enable button the Agent Maintenance Availability will be enabled for this agent.
3 | The currently defined Availability will become the agents new Regular Availability.
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceLink/index_de.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/agent-maintenance-plugin/main/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceLink/index_de.properties
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | - package-ecosystem: "github-actions"
8 | directory: /
9 | schedule:
10 | interval: "weekly"
11 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceAction/config_de.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/agent-maintenance-plugin/main/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceAction/config_de.properties
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceAction/index_de.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/agent-maintenance-plugin/main/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceAction/index_de.properties
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceOfflineCause/cause_de.properties:
--------------------------------------------------------------------------------
1 | maintenanceover=Das Wartungsfenster wurde nicht gefunden. Wahrscheinlich ist es gerade zu Ende gegangen.
2 | Start\ Time=Startzeit
3 | End\ Time=Endzeit
4 | Reason=Grund
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceWindow/config_de.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/agent-maintenance-plugin/main/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceWindow/config_de.properties
--------------------------------------------------------------------------------
/src/main/webapp/help/help-keepUpWhenActive_de.html:
--------------------------------------------------------------------------------
1 | Wenn diese Option aktiviert ist und dieser Agent offline geschaltet werden soll, aber Builds ausgeführt werden, wartet Jenkins,
2 | bis diese Builds abgeschlossen sind, bevor dieser Agent offline geschaltet wird.
3 |
--------------------------------------------------------------------------------
/src/main/webapp/help/help-disable_de.html:
--------------------------------------------------------------------------------
1 |
2 | Durch betätigen des Ausschalten Kopfes werden alle existierenden Wartungsfenster dieses Agenten gelöscht.
3 | Die momentan definierte Reguläre Verfügbarkeit wird die neue Verfügbarkeit des Agenten.
4 |
2 | Durch betätigen des Einschalten Knopfes wird die Agentenwartungsverfügbarkeit für diesen Agenten eingeschaltet.
3 | Die momenan definierte Verfügbarkeit wird die neue Reguläre Verfügbarkeit des Agenten.
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/RecurringMaintenanceWindow/config_de.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/agent-maintenance-plugin/main/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/RecurringMaintenanceWindow/config_de.properties
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/AgentMaintenanceRetentionStrategy/config_de.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/agent-maintenance-plugin/main/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/AgentMaintenanceRetentionStrategy/config_de.properties
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/RecurringMaintenanceWindow/config.properties:
--------------------------------------------------------------------------------
1 | Reason=Reason
2 | keepUpWhenActive=Keep agent online while jobs are running
3 | maxWaitMinutes=Max waiting time in minutes for builds to finish
4 | takeOnline=Start agent automatically after maintenance
--------------------------------------------------------------------------------
/src/main/webapp/help/help-label.html:
--------------------------------------------------------------------------------
1 |
2 | A label expression selecting the agents for which to apply the maintenance window.
3 | The maintenance window will only be applied to those agents that have agent maintenance as availability configured and
4 | for which the user has configure permissions.
5 |
3 | This plugin allows to take agents offline for one or more dedicated time windows on specific dates, e.g. due to a hardware maintenance.
4 | It is possible to define other agent availabilities, e.g. to take agent offline on a schedule.
5 |
2 | Ein Label-Ausdruck, der die Agenten auswählt, für die das Wartungsfenster angewendet werden soll.
3 | Das Wartungsfenster wird nur auf die Agenten angewendet, für die Agentenwartung als Verfügbarkeit konfiguriert ist und
4 | für die der Benutzer Konfigurationsberechtigungen hat.
5 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceWindow/config.properties:
--------------------------------------------------------------------------------
1 | endTime= End Time
2 | startTime= Start Time
3 | Reason=Reason
4 | keepUpWhenActive=Keep agent online while jobs are running
5 | maxWaitMinutes=Max waiting time in minutes for builds to finish
6 | takeOnline=Start agent automatically after maintenance
--------------------------------------------------------------------------------
/src/main/webapp/help/help-takeOnline.html:
--------------------------------------------------------------------------------
1 | Automatically take the agent online according to its default availability after the maintenance is finished.
2 | If this option is not checked the agent will stay disconnected until it is manually launched (or Jenkins is restarted).
3 | During the maintenance window a manual launch is not possible.
--------------------------------------------------------------------------------
/src/main/webapp/help/help-takeOnline_de.html:
--------------------------------------------------------------------------------
1 | Nimmt den Agenten nach Abschluss der Wartung gemäß seiner Standardverfügbarkeit automatisch online.
2 | Wenn diese Option nicht aktiviert ist, bleibt der Agent getrennt, bis er manuell gestartet wird (oder Jenkins neu gestartet wird).
3 | Während des Wartungsfensters ist ein manueller Start nicht möglich.
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceWindow/help-endTime.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceWindow/help-endTime_de.properties:
--------------------------------------------------------------------------------
1 | endTime=Datum und Uhrzeit zu der das Wartungsfenster enden soll in der Zeitzone der Jenkins Controller JVM (aktuell {0}). \
2 | Im Format yyyy-MM-dd HH:mm \
3 | Das Wartungsfenster wird automatisch entfernt, wenn die Wartung endet.
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceWindow/help-endTime.properties:
--------------------------------------------------------------------------------
1 | endTime=The date and time when the maintenance ends in the time zone of the Jenkins controller JVM (currently {0}). \
2 | Formatted as yyyy-MM-dd HH:mm \
3 | The maintenance window will be automatically removed when the maintenance ends.
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceWindow/help-startTime.properties:
--------------------------------------------------------------------------------
1 | startTime=The date and time when the maintenance starts in the time zone of the Jenkins controller JVM (currently {0}). \
2 | Formatted as yyyy-MM-dd HH:mm \
3 | From this point in time Jenkins will not allow new builds to execute on this agent.
4 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/RecurringMaintenanceWindow/help-startTimeSpec.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
2 | Take this agent offline during dedicated time windows, e.g. due to maintenance activities. Outside of an active maintenance window, the "Regular Availability" is applied.
3 | Exact behaviour what to do with running builds can be individually defined for each maintenanance window.
4 |
2 | The downtime in minutes for the maintenance window.
3 | The format is '<integer>' in minutes, '<integer><unit>' or a combination of '<integer><unit>',
4 | where <unit> is one of 'm' (minutes), 'h' (hours) or 'd' (days).
5 | Example: '1d 4h 30m' means one day, four hours and 30 minutes which evaluates to 1710 minutes.
6 | 1710 is then the value that will be displayed later.
7 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yaml:
--------------------------------------------------------------------------------
1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins
2 |
3 | name: cd
4 | on:
5 | workflow_dispatch:
6 | check_run:
7 | types:
8 | - completed
9 |
10 | jobs:
11 | maven-cd:
12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1
13 | secrets:
14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
16 |
--------------------------------------------------------------------------------
/src/main/webapp/help/help-maxWaitMinutes.html:
--------------------------------------------------------------------------------
1 | The time in minutes to wait before aborting running builds.
2 | Set to a negative value to wait indefinitely.
3 | The format is '<integer>' in minutes, '<integer><unit>' or a combination of '<integer><unit>',
4 | where <unit> is one of 'm' (minutes), 'h' (hours) or 'd' (days).
5 | Example: '1d 4h 30m' means one day, four hours and 30 minutes which evaluates to 1710 minutes.
6 | 1710 is then the value that will be displayed later.
--------------------------------------------------------------------------------
/.build-config/checkstyle-suppressions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
10 |
12 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main/java/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceInterruption.java:
--------------------------------------------------------------------------------
1 | package com.sap.prd.jenkins.plugins.agent_maintenance;
2 |
3 | import jenkins.model.CauseOfInterruption;
4 |
5 | /** Agent is down for maintenance. */
6 | public class MaintenanceInterruption extends CauseOfInterruption {
7 | private static final long serialVersionUID = 1L;
8 |
9 | @Override
10 | public String getShortDescription() {
11 | return "Agent is going down for scheduled maintenance";
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceConfiguration/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/webapp/help/help-maxWaitMinutes_de.html:
--------------------------------------------------------------------------------
1 | Die Wartezeit in Minuten, bevor laufende Builds abgebrochen werden.
2 | Legen Sie einen negativen Wert fest, um unbegrenzt zu warten.
3 | Das Format ist '<Ganzzahl>' in Minuten, '<Ganzzahl><Einheit>' oder eine Kombination aus '<Ganzzahl><Einheit>',
4 | wobei <Einheit> entweder 'm' (Minuten), 'h' (Stunden) oder 'd' (Tage) sein muss. Beispiel: '1d 4h 30m' bedeutet einen Tag,
5 | vier Stunden und 30 Minuten, was 1710 Minuten ergibt. 1710 ist dann der Wert der später angezeigt wird.
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceConfiguration/help-injectRetentionstrategy.html:
--------------------------------------------------------------------------------
1 |
2 | Inject the agent maintenance availability automatically to all newly created and updated agents if not already configured.
3 | For existing agents, the currently defined availability will be set as the default strategy.
4 | This will not inject the agent maintenance availability to cloud agents.
5 | Use the button Inject to set the agent maintenance availability for all existing agents if not already configured.
6 | Use the button Remove to recover the originally defined availability.
7 |
--------------------------------------------------------------------------------
/.github/workflows/jenkins-security-scan.yml:
--------------------------------------------------------------------------------
1 | name: Jenkins Security Scan
2 | on:
3 | push:
4 | branches:
5 | - "main"
6 | pull_request:
7 | types: [ opened, synchronize, reopened ]
8 | workflow_dispatch:
9 |
10 | permissions:
11 | security-events: write
12 | contents: read
13 | actions: read
14 |
15 | jobs:
16 | security-scan:
17 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2
18 | with:
19 | java-cache: '' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.
20 | # java-version: 11 # What version of Java to set up for the build.
21 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceConfiguration/help-injectRetentionstrategy_de.html:
--------------------------------------------------------------------------------
1 |
2 | Injiziere die Agentenwartungsverfügbarkeit automatisch in alle neu erstellten und aktualisierten Agenten,
3 | falls diese noch nicht konfiguriert ist.
4 | Für bestehende Agenten wird die aktuell definierte Verfügbarkeit als Standardstrategie eingestellt.
5 | Dies fügt die Agentenwartungsverfügbarkeit nicht zu Cloud-Agenten hinzu.
6 | Verwenden Sie die Schaltfläche Einfügen, um die Agentenwartungsverfügbarkeit für alle vorhandenen Agenten festzulegen,
7 | falls noch nicht konfiguriert.
8 | Verwenden Sie die Schaltfläche Entfernen, um die ursprünglich definierte Verfügbarkeit wiederherzustellen.
9 |
18 |
--------------------------------------------------------------------------------
/src/main/resources/lib/permissions/hasAnyPermission.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Renders the body only if the current user has any of the specified permissions
5 |
6 | By default it will reuse the current context.
7 | If the provided value does not inherit from hudson.security.AccessControlled,
8 | the tag will look for the first ancestor satisfying the condition.
9 | The hasAnyPermission will be performed against that value.
10 |
11 |
12 | permissions object to check. If this is null, the body will be also rendered.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/resources/lib/permissions/hasNoPermission.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Renders the body only if the current user has any of the specified permissions
5 |
6 | By default it will reuse the current context.
7 | If the provided value does not inherit from hudson.security.AccessControlled,
8 | the tag will look for the first ancestor satisfying the condition.
9 | The hasAnyPermission will be performed against that value.
10 |
11 |
12 | permissions object to check. If this is null, the body will be also rendered.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/test/java/com/sap/prd/jenkins/plugins/agent_maintenance/RecurringTest.java:
--------------------------------------------------------------------------------
1 | package com.sap.prd.jenkins.plugins.agent_maintenance;
2 |
3 | import static org.hamcrest.MatcherAssert.assertThat;
4 | import static org.hamcrest.Matchers.is;
5 |
6 | import hudson.model.Slave;
7 | import org.junit.jupiter.api.Test;
8 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
9 |
10 | /** Test recurring maintenance windows. */
11 | @WithJenkins
12 | class RecurringTest extends BaseIntegrationTest {
13 |
14 | @Test
15 | void recurringMaintenanceInjectsMaintenance() throws Exception {
16 | String agentName = "recurring";
17 | Slave agent = getAgent(agentName);
18 | RecurringMaintenanceWindow rw = new RecurringMaintenanceWindow("0 2 * * *",
19 | "test", true, true, "10m", "60m", "test", null, 0);
20 | maintenanceHelper.addRecurringMaintenanceWindow(agent.getNodeName(), rw);
21 | triggerCheckCycle(agent);
22 | assertThat(maintenanceHelper.hasMaintenanceWindows(agentName), is(true));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 | schedule:
9 | - cron: "37 10 * * 2"
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | actions: read
17 | contents: read
18 | security-events: write
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | language: [ javascript, java ]
24 |
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v5
28 |
29 | - name: Initialize CodeQL
30 | uses: github/codeql-action/init@v4
31 | with:
32 | languages: ${{ matrix.language }}
33 | queries: +security-and-quality
34 |
35 | - name: Autobuild
36 | uses: github/codeql-action/autobuild@v4
37 |
38 | - name: Perform CodeQL Analysis
39 | uses: github/codeql-action/analyze@v4
40 | with:
41 | category: "/language:${{ matrix.language }}"
42 |
--------------------------------------------------------------------------------
/src/test/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MigrationTest/nodes/agent/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | agent
4 |
5 | \mock-agents\agent
6 | 1
7 | NORMAL
8 |
9 |
10 | 5
11 | 1
12 |
13 |
14 |
15 | 0
16 | 0
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/test/java/com/sap/prd/jenkins/plugins/agent_maintenance/MigrationTest.java:
--------------------------------------------------------------------------------
1 | package com.sap.prd.jenkins.plugins.agent_maintenance;
2 |
3 | import static org.hamcrest.MatcherAssert.assertThat;
4 | import static org.hamcrest.Matchers.instanceOf;
5 | import static org.hamcrest.Matchers.is;
6 |
7 | import hudson.model.Node;
8 | import hudson.slaves.RetentionStrategy;
9 | import java.io.IOException;
10 | import org.junit.jupiter.api.Test;
11 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
12 | import org.jvnet.hudson.test.recipes.LocalData;
13 |
14 | /**
15 | * Tests that the old data format for maintenance windows is properly read.
16 | */
17 | @WithJenkins
18 | class MigrationTest extends BaseIntegrationTest {
19 |
20 | @Test
21 | @LocalData
22 | void readOldData() throws IOException {
23 | Node agent = rule.jenkins.getNode("agent");
24 | RetentionStrategy retentionStrategy = agent.toComputer().getRetentionStrategy();
25 | assertThat(retentionStrategy, instanceOf(AgentMaintenanceRetentionStrategy.class));
26 | assertThat(MaintenanceHelper.getInstance().hasMaintenanceWindows("agent"), is(true));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceActionFactory.java:
--------------------------------------------------------------------------------
1 | package com.sap.prd.jenkins.plugins.agent_maintenance;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import hudson.Extension;
5 | import hudson.model.Action;
6 | import hudson.slaves.AbstractCloudComputer;
7 | import hudson.slaves.SlaveComputer;
8 | import java.util.ArrayList;
9 | import java.util.Collection;
10 | import java.util.List;
11 | import jenkins.model.TransientActionFactory;
12 |
13 | /** Inject the action link to agents. */
14 | @Extension
15 | public class MaintenanceActionFactory extends TransientActionFactory {
16 |
17 | @Override
18 | @NonNull
19 | public Collection extends Action> createFor(@NonNull SlaveComputer target) {
20 | List result = new ArrayList<>();
21 | if (!(target instanceof AbstractCloudComputer)
22 | && target.getActions().stream().noneMatch(x -> x instanceof MaintenanceAction)) {
23 | MaintenanceAction action = new MaintenanceAction(target);
24 | result.add(action);
25 | target.addAction(new MaintenanceAction(target));
26 | }
27 | return result;
28 | }
29 |
30 | @Override
31 | public Class type() {
32 | return SlaveComputer.class;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/agent-maintenance.css:
--------------------------------------------------------------------------------
1 | /* The popup form - hidden by default */
2 | .am__modal {
3 | display: none;
4 | }
5 |
6 | .am__table
7 | {
8 | width: auto;
9 | margin-bottom: 0px;
10 | }
11 |
12 | .am__action-delete,
13 | .am__action-delete-recurring,
14 | .am__link-delete
15 | {
16 | cursor: pointer;
17 | }
18 |
19 | .am__table > tbody > tr.active > td
20 | {
21 | background: var(--medium-grey);
22 | }
23 |
24 |
25 | .am__table > thead > tr > th
26 | {
27 | padding-right: 5px;
28 | padding-left: 10px;
29 | }
30 |
31 | .am__table > tbody > tr > td
32 | {
33 | padding-right: 5px;
34 | padding-left: 10px;
35 | }
36 |
37 | .am__checkbox
38 | {
39 | vertical-align: middle;
40 | }
41 |
42 | .right {
43 | text-align: right;
44 | }
45 |
46 | .am__select {
47 | color: var(--link-color);
48 | text-decoration: underline;
49 | cursor: pointer;
50 | }
51 |
52 | .am__table-icon {
53 | margin-bottom: 2px;
54 | }
55 |
56 | .jenkins-checkbox {
57 | vertical-align: middle;
58 | }
59 |
60 | .am__div--break {
61 | flex-basis: 100%;
62 | height: 0;
63 | }
64 |
65 | #am__div--select {
66 | flex-grow: 1;
67 | }
68 |
69 | .jenkins-buttons-row {
70 | flex-wrap: wrap;
71 | row-gap: 5px;
72 | }
73 |
74 | .jenkins-dialog__contents {
75 | padding-top: 5px;
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceDefinitions.java:
--------------------------------------------------------------------------------
1 | package com.sap.prd.jenkins.plugins.agent_maintenance;
2 |
3 | import java.util.Set;
4 | import java.util.SortedSet;
5 |
6 | /**
7 | * Container that holds the scheduled and recurring maintenance windows for an agent.
8 | */
9 | public class MaintenanceDefinitions {
10 | private final SortedSet scheduled;
11 | private final Set recurring;
12 |
13 | /**
14 | * Create definitions container.
15 | *
16 | * @param scheduled A set of scheduled maintenance windows
17 | * @param recurring A set of recurring maintenance windows
18 | */
19 | public MaintenanceDefinitions(SortedSet scheduled, Set recurring) {
20 | this.scheduled = scheduled;
21 | this.recurring = recurring;
22 | }
23 |
24 | /**
25 | * Get the scheduled maintenance windows.
26 | *
27 | * @return Set of scheduled maintenance windows
28 | */
29 | public SortedSet getScheduled() {
30 | return scheduled;
31 | }
32 |
33 | /**
34 | * Get the recurring maintenance windows.
35 | *
36 | * @return Set of recurring maintenance windows
37 | */
38 | public Set getRecurring() {
39 | return recurring;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/AgentMaintenanceRetentionStrategy/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
12 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/java/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceOfflineCause.java:
--------------------------------------------------------------------------------
1 | package com.sap.prd.jenkins.plugins.agent_maintenance;
2 |
3 | import hudson.slaves.OfflineCause;
4 |
5 | /** Offline cause because of a maintenance. */
6 | public class MaintenanceOfflineCause extends OfflineCause {
7 |
8 | private MaintenanceWindow maintenanceWindow;
9 | private final String computerName;
10 |
11 | public MaintenanceOfflineCause(MaintenanceWindow maintenanceWindow, String computerName) {
12 | this.maintenanceWindow = maintenanceWindow;
13 | this.computerName = computerName;
14 | }
15 |
16 | private void updateMaintenanceWindow() {
17 | MaintenanceWindow newMaintenanceWindow = MaintenanceHelper.getInstance().getMaintenanceWindow(computerName, maintenanceWindow.getId());
18 | if (newMaintenanceWindow != null) {
19 | maintenanceWindow = newMaintenanceWindow;
20 | }
21 | }
22 |
23 | public String getStartTime() {
24 | updateMaintenanceWindow();
25 | return maintenanceWindow.getStartTime();
26 | }
27 |
28 | public String getEndTime() {
29 | updateMaintenanceWindow();
30 | return maintenanceWindow.getEndTime();
31 | }
32 |
33 | public String getReason() {
34 | updateMaintenanceWindow();
35 | return maintenanceWindow.getReason();
36 | }
37 |
38 | public boolean isTakeOnline() {
39 | return maintenanceWindow.isTakeOnline();
40 | }
41 |
42 | @Override
43 | public String toString() {
44 | return getReason();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to the Agent Maintenance Plugin
2 |
3 | Plugin source code is hosted on [GitHub](https://github.com/jenkinsci/agent-maintenance-plugin).
4 | New feature proposals and bug fix proposals should be submitted as
5 | [GitHub pull requests](https://help.github.com/articles/creating-a-pull-request).
6 | Your pull request will be evaluated by the [Jenkins job](https://ci.jenkins.io/job/Plugins/job/agent-maintenance-plugin/).
7 |
8 | Before submitting your change, please assure that you've added tests that verify the change.
9 |
10 | ## Code Coverage
11 |
12 | Code coverage reporting is available as a maven target.
13 | Please try to improve code coverage with tests when you submit.
14 |
15 | * `mvn -P enable-jacoco clean install jacoco:report` to report code coverage
16 |
17 | ## Static Analysis
18 |
19 | Please don't introduce new spotbugs output.
20 |
21 | * `mvn spotbugs:check` to analyze project using [Spotbugs](https://spotbugs.github.io)
22 | * `mvn spotbugs:gui` to review report using GUI
23 |
24 | ## Code Formatting
25 |
26 | Code formatting is checked by checkstyle. If the formatting is not correct, the build will fail.
27 | The rules are basically the Google Java formatting rules with some smaller relaxations.
28 |
29 |
30 | ## File format
31 |
32 | Files in the repository are in Unix format (LF line terminators).
33 | Please continue using Unix file format for consistency.
34 |
35 | ## Reporting Issues
36 |
37 | Report issues in the [Jenkins issue tracker](https://www.jenkins.io/participate/report-issue/redirect/#(tbd)).
--------------------------------------------------------------------------------
/src/main/resources/images/symbols/maintenance.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/java/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceNodeListener.java:
--------------------------------------------------------------------------------
1 | package com.sap.prd.jenkins.plugins.agent_maintenance;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import hudson.Extension;
5 | import hudson.model.Node;
6 | import hudson.model.Slave;
7 | import hudson.slaves.AbstractCloudSlave;
8 | import jenkins.model.NodeListener;
9 |
10 | /** Listener to react on events for agents. */
11 | @Extension
12 | public class MaintenanceNodeListener extends NodeListener {
13 | MaintenanceHelper helper = MaintenanceHelper.getInstance();
14 |
15 | @Override
16 | protected void onCreated(@NonNull Node node) {
17 | if (node instanceof Slave && !(node instanceof AbstractCloudSlave)) {
18 | helper.createAgent(node.getNodeName());
19 | if (MaintenanceConfiguration.getInstance().isInjectRetentionStrategy()) {
20 | helper.injectRetentionStrategy(node.toComputer());
21 | }
22 | }
23 | }
24 |
25 | @Override
26 | protected void onDeleted(@NonNull Node node) {
27 | if (node instanceof Slave) {
28 | helper.deleteAgent(node.getNodeName());
29 | }
30 | }
31 |
32 | @Override
33 | protected void onUpdated(@NonNull Node oldNode, @NonNull Node newNode) {
34 | if (newNode instanceof Slave && !(newNode instanceof AbstractCloudSlave)) {
35 | if (!oldNode.getNodeName().equals(newNode.getNodeName())) {
36 | helper.renameAgent(oldNode.getNodeName(), newNode.getNodeName());
37 | }
38 | if (MaintenanceConfiguration.getInstance().isInjectRetentionStrategy()) {
39 | helper.injectRetentionStrategy(newNode.toComputer());
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/.reuse/dep5:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Upstream-Name: agent-maintenance-plugin
3 | Upstream-Contact: m.winter_at_sap.com
4 | Source: https://github.com/sap/agent-maintenance-plugin
5 | Disclaimer: The code in this project may include calls to APIs ("API Calls") of
6 | SAP or third-party products or services developed outside of this project
7 | ("External Products").
8 | "APIs" means application programming interfaces, as well as their respective
9 | specifications and implementing code that allows software to communicate with
10 | other software.
11 | API Calls to External Products are not licensed under the open source license
12 | that governs this project. The use of such API Calls and related External
13 | Products are subject to applicable additional agreements with the relevant
14 | provider of the External Products. In no event shall the open source license
15 | that governs this project grant any rights in or to any External Products,or
16 | alter, expand or supersede any terms of the applicable additional agreements.
17 | If you have a valid license agreement with SAP for the use of a particular SAP
18 | External Product, then you may make use of any API Calls included in this
19 | project's code for that SAP External Product, subject to the terms of such
20 | license agreement. If you do not have a valid license agreement for the use of
21 | a particular SAP External Product, then you may only make use of any API Calls
22 | in this project for that SAP External Product for your internal, non-productive
23 | and non-commercial test and evaluation of such API Calls. Nothing herein grants
24 | you any rights to use or access any SAP External Product, or provide any third
25 | parties the right to use of access any SAP External Product, through API Calls.
26 |
27 | Files: **
28 | Copyright: 2022 SAP SE or an SAP affiliate company and agent-maintenance-plugin contributors
29 | License: Apache-2.0
30 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/RecurringMaintenanceWindow/help-startTimeSpec.properties:
--------------------------------------------------------------------------------
1 | startTimeSpec=This field follows the syntax of cron (with some differences) and deviates from the syntax used at other places in Jenkins: \
2 |
MINUTE HOUR DOM MONTH DOW
\
3 |
\
4 |
A field may be an asterisk (*), which always stands for "first-last". For the "day of the month" or "day of the week" fields, a question mark (?) may be used instead of an asterisk.
\
5 |
Ranges of numbers are expressed by two numbers separated with a hyphen (-). The specified range is inclusive.
\
6 |
Following a range (or *) with /n specifies the interval of the number''s value through the range.
\
7 |
English names can also be used for the "month" and "day of week" fields. Use the first three letters of the particular day or month (case does not matter).
\
8 |
The "day of month" and "day of week" fields can contain a L-character, which stands for "last", and has a different meaning in each field:\
9 |
\
10 |
In the "day of month" field, L stands for "the last day of the month". If followed by an negative offset (i.e. L-n), it means "nth-to-last day of the month". If followed by W (i.e. LW), it means "the last weekday of the month".
\
11 |
In the "day of week" field, dL or DDDL stands for "the last day of week d (or DDD) in the month".
\
12 |
\
13 |
The "day of month" field can be nW, which stands for "the nearest weekday to day of the month n". If n falls on Saturday, this yields the Friday before it. If n falls on Sunday, this yields the Monday after, which also happens if n is 1 and falls on a Saturday (i.e. 1W stands for "the first weekday of the month").
\
14 |
The "day of week" field can be d#n (or DDD#n), which stands for "the n-th day of week d (or DDD) in the month".
108 |
109 |
110 |
111 |
113 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.jenkins-ci.plugins
7 | plugin
8 | 5.27
9 |
10 |
11 |
12 | io.jenkins.plugins
13 | agent-maintenance
14 | ${revision}.${changelist}
15 | hpi
16 |
17 | Agent Maintenance
18 | Define time windows on which agents will be taken offline for maintenance.
19 | https://github.com/jenkinsci/agent-maintenance-plugin
20 |
21 |
22 | Apache License, Version 2.0
23 | https://opensource.org/licenses/Apache-2.0
24 |
25 |
26 |
27 |
28 | scm:git:https://github.com/${gitHubRepo}.git
29 | scm:git:git@github.com:${gitHubRepo}.git
30 | https://github.com/${gitHubRepo}
31 | ${scmTag}
32 |
33 |
34 |
35 | 2
36 | 999999-SNAPSHOT
37 | jenkinsci/agent-maintenance-plugin
38 |
39 | 2.516
40 | ${jenkins.baseline}.1
41 | 1.6.1
42 | 12.0.1
43 | false
44 | 2.0
45 |
46 |
47 |
48 |
49 |
50 | io.jenkins.tools.bom
51 | bom-${jenkins.baseline}.x
52 | 5543.vfd758c7c868d
53 | pom
54 | import
55 |
56 |
57 |
58 |
59 |
60 | io.jenkins.plugins
61 | ionicons-api
62 |
63 |
64 | io.jenkins.plugins
65 | flatpickr-api
66 | 4.6.13-18.vcf5f6a_5b_8468
67 |
68 |
69 | com.cronutils
70 | cron-utils
71 | 9.2.1
72 |
73 |
74 | org.mockito
75 | mockito-junit-jupiter
76 | test
77 |
78 |
79 | com.github.stephenc.jcip
80 | jcip-annotations
81 | 1.0-1
82 | test
83 |
84 |
85 | org.jenkins-ci.plugins
86 | mock-slave
87 | 193.v88c279d0c584
88 | test
89 |
90 |
91 | org.jenkins-ci.plugins
92 | mock-security-realm
93 | 106.va_5286004db_2e
94 | test
95 |
96 |
97 | org.jenkins-ci.plugins
98 | matrix-auth
99 | test
100 |
101 |
102 |
103 |
104 |
105 | repo.jenkins-ci.org
106 | https://repo.jenkins-ci.org/public/
107 |
108 |
109 |
110 |
111 | repo.jenkins-ci.org
112 | https://repo.jenkins-ci.org/public/
113 |
114 |
115 |
116 |
117 |
118 |
119 | org.codehaus.mojo
120 | tidy-maven-plugin
121 | 1.4.0
122 |
123 |
124 | validate
125 | validate
126 |
127 | check
128 |
129 |
130 |
131 |
132 |
133 | org.apache.maven.plugins
134 | maven-checkstyle-plugin
135 | 3.6.0
136 |
137 |
138 | com.puppycrawl.tools
139 | checkstyle
140 | ${checkstyle.version}
141 |
142 |
143 |
144 | ${project.basedir}/.build-config/checkstyle.xml
145 | ${project.basedir}/.build-config/checkstyle-suppressions.xml
146 | checkstyle.suppressions.file
147 | true
148 | true
149 |
150 |
151 |
152 | compile-checkstyle
153 |
154 | checkstyle
155 |
156 | compile
157 |
158 |
159 | test-check
160 |
161 | check
162 |
163 | test
164 |
165 | warning
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/src/main/java/com/sap/prd/jenkins/plugins/agent_maintenance/AgentMaintenanceRetentionStrategy.java:
--------------------------------------------------------------------------------
1 | package com.sap.prd.jenkins.plugins.agent_maintenance;
2 |
3 | import edu.umd.cs.findbugs.annotations.CheckForNull;
4 | import edu.umd.cs.findbugs.annotations.NonNull;
5 | import hudson.Extension;
6 | import hudson.model.Descriptor;
7 | import hudson.model.DescriptorVisibilityFilter;
8 | import hudson.model.Executor;
9 | import hudson.model.Queue;
10 | import hudson.model.Result;
11 | import hudson.model.Slave;
12 | import hudson.slaves.OfflineCause;
13 | import hudson.slaves.RetentionStrategy;
14 | import hudson.slaves.SlaveComputer;
15 | import java.util.List;
16 | import java.util.logging.Level;
17 | import java.util.logging.Logger;
18 | import net.jcip.annotations.GuardedBy;
19 | import org.jenkinsci.Symbol;
20 | import org.kohsuke.stapler.DataBoundConstructor;
21 |
22 | /**
23 | * {@link RetentionStrategy} that allows to take an agent offline for a defined time window for maintenance.
24 | */
25 | public class AgentMaintenanceRetentionStrategy extends RetentionStrategy {
26 |
27 | private static final Logger LOGGER = Logger.getLogger(AgentMaintenanceRetentionStrategy.class.getName());
28 |
29 | private RetentionStrategy regularRetentionStrategy;
30 |
31 | @DataBoundConstructor
32 | public AgentMaintenanceRetentionStrategy(RetentionStrategy regularRetentionStrategy) {
33 | this.regularRetentionStrategy = regularRetentionStrategy;
34 | }
35 |
36 | public RetentionStrategy> getRegularRetentionStrategy() {
37 | return regularRetentionStrategy;
38 | }
39 |
40 | public void setRegularRetentionStrategy(RetentionStrategy regularRetentionStrategy) {
41 | this.regularRetentionStrategy = regularRetentionStrategy;
42 | }
43 |
44 | @Override
45 | public boolean isAcceptingTasks(SlaveComputer c) {
46 | MaintenanceWindow maintenance = MaintenanceHelper.getInstance().getMaintenance(c.getName());
47 | if (maintenance != null) {
48 | return false;
49 | }
50 | return regularRetentionStrategy.isAcceptingTasks(c);
51 | }
52 |
53 | @Override
54 | public boolean isManualLaunchAllowed(final SlaveComputer c) {
55 | MaintenanceWindow maintenance = MaintenanceHelper.getInstance().getMaintenance(c.getName());
56 | if (maintenance != null) {
57 | return false;
58 | }
59 | return regularRetentionStrategy.isManualLaunchAllowed(c);
60 | }
61 |
62 | @Override
63 | @GuardedBy("hudson.model.Queue.lock")
64 | public synchronized long check(final SlaveComputer c) {
65 | MaintenanceWindow maintenance = MaintenanceHelper.getInstance().getMaintenance(c.getName());
66 | MaintenanceHelper.getInstance().checkRecurring(c.getName());
67 | LOGGER.log(Level.FINER, "Checking for Maintenance Window for agent {0}. online = {1}, idle = {2}",
68 | new Object[] { c.getName(), c.isOnline(), c.isIdle() });
69 | if (maintenance != null) {
70 | LOGGER.log(Level.FINE, "Active Maintenance Window found for agent {0}: startTime = {1}, endTime = {2}",
71 | new Object[] { c.getName(), maintenance.getStartTime(), maintenance.getEndTime() });
72 |
73 | if (c.isOnline()) {
74 | if (maintenance.isKeepUpWhenActive()) {
75 | if (!maintenance.isMaxWaitTimeFinished()) {
76 | if (c.isIdle()) {
77 | Queue.withLock(new Runnable() {
78 | @Override
79 | public void run() {
80 | LOGGER.log(Level.INFO, "Disconnecting agent {0} as it was idle when maintenance window started.",
81 | new Object[] { c.getName() });
82 | c.disconnect(maintenance.getOfflineCause(c.getName()));
83 | }
84 | });
85 | }
86 | } else {
87 | if (maintenance.buildsHaveBeenAborted()) {
88 | LOGGER.log(Level.INFO,
89 | "Disconnecting agent {0} as it has finished its scheduled uptime and max waiting time for builds to finish is over",
90 | new Object[] { c.getName() });
91 | c.disconnect(maintenance.getOfflineCause(c.getName()));
92 | } else {
93 | LOGGER.log(Level.INFO, "Aborting running builds on agent {0} as it has finished its scheduled uptime "
94 | + "and max waiting time for builds to finish is over", new Object[] { c.getName() });
95 | for (Executor e : c.getExecutors()) {
96 | if (e.isBusy()) {
97 | e.interrupt(Result.ABORTED, new MaintenanceInterruption());
98 | }
99 | }
100 | maintenance.setAborted(true);
101 | }
102 | }
103 | } else {
104 | if (maintenance.buildsHaveBeenAborted()) {
105 | // no need to get the queue lock as the user
106 | // has selected the break builds
107 | // option!
108 | LOGGER.log(Level.INFO, "Disconnecting agent {0} as it has finished its scheduled uptime", new Object[] { c.getName() });
109 | c.disconnect(maintenance.getOfflineCause(c.getName()));
110 | } else {
111 | LOGGER.log(Level.INFO, "Aborting running builds on agent {0} as it has finished its scheduled uptime",
112 | new Object[] { c.getName() });
113 | for (Executor e : c.getExecutors()) {
114 | if (e.isBusy()) {
115 | e.interrupt(Result.ABORTED, new MaintenanceInterruption());
116 | }
117 | }
118 | maintenance.setAborted(true);
119 | }
120 | }
121 | }
122 | } else {
123 | if (c.isOffline()) {
124 | OfflineCause oc = c.getOfflineCause();
125 | if (oc instanceof MaintenanceOfflineCause moc) {
126 | if (!moc.isTakeOnline()) {
127 | LOGGER.log(Level.INFO, "Computer should not be taken online automatically: {0}", c.getName());
128 | return 5;
129 | }
130 | }
131 | }
132 | return regularRetentionStrategy.check(c);
133 | }
134 | return 1;
135 | }
136 |
137 | /** Descriptor for UI only. */
138 | @Extension
139 | @Symbol("agent-maintenance")
140 | public static class DescriptorImpl extends Descriptor> {
141 | @Override
142 | @NonNull
143 | public String getDisplayName() {
144 | return Messages.AgentMaintenanceRetentionStrategy_displayName();
145 | }
146 |
147 | /**
148 | * For UI only.
149 | *
150 | * @param it the agent instance
151 | * @return List of descriptors
152 | */
153 | public final List>> retentionStrategyDescriptors(@CheckForNull Slave it) {
154 | List>> descriptors = it == null
155 | ? DescriptorVisibilityFilter.applyType(Slave.class, RetentionStrategy.all())
156 | : DescriptorVisibilityFilter.apply(it, RetentionStrategy.all());
157 | descriptors.remove(this);
158 | return descriptors;
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/main/java/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceLink.java:
--------------------------------------------------------------------------------
1 | package com.sap.prd.jenkins.plugins.agent_maintenance;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import hudson.Extension;
5 | import hudson.model.AutoCompletionCandidates;
6 | import hudson.model.Computer;
7 | import hudson.model.Label;
8 | import hudson.model.ManagementLink;
9 | import hudson.model.Node;
10 | import hudson.model.labels.LabelExpression;
11 | import hudson.security.Permission;
12 | import hudson.slaves.AbstractCloudComputer;
13 | import hudson.slaves.SlaveComputer;
14 | import hudson.util.FormValidation;
15 | import jakarta.servlet.ServletException;
16 | import java.io.IOException;
17 | import java.io.PrintWriter;
18 | import java.io.StringWriter;
19 | import java.util.ArrayList;
20 | import java.util.HashMap;
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.Map.Entry;
24 | import java.util.Objects;
25 | import java.util.Set;
26 | import java.util.logging.Level;
27 | import java.util.logging.Logger;
28 | import jenkins.management.Badge;
29 | import jenkins.model.Jenkins;
30 | import net.sf.json.JSONObject;
31 | import org.kohsuke.accmod.Restricted;
32 | import org.kohsuke.accmod.restrictions.NoExternalUse;
33 | import org.kohsuke.stapler.QueryParameter;
34 | import org.kohsuke.stapler.StaplerRequest2;
35 | import org.kohsuke.stapler.StaplerResponse2;
36 | import org.kohsuke.stapler.bind.JavaScriptMethod;
37 | import org.kohsuke.stapler.verb.POST;
38 |
39 | /**
40 | * Link on manage Jenkins page to list all maintenance windows of all agents.
41 | */
42 | @Extension
43 | public class MaintenanceLink extends ManagementLink {
44 | private static final Logger LOGGER = Logger.getLogger(MaintenanceLink.class.getName());
45 |
46 | private transient Throwable error;
47 |
48 | @Override
49 | public String getDescription() {
50 | return Messages.MaintenanceLink_description();
51 | }
52 |
53 | @Override
54 | public String getDisplayName() {
55 | return Messages.MaintenanceLink_displayName();
56 | }
57 |
58 | @Override
59 | public String getIconFileName() {
60 | return "symbol-maintenance plugin-agent-maintenance";
61 | }
62 |
63 | @Override
64 | public String getUrlName() {
65 | return "agent-maintenances";
66 | }
67 |
68 | @Override
69 | @NonNull
70 | public Permission getRequiredPermission() {
71 | return Jenkins.SYSTEM_READ;
72 | }
73 |
74 | /**
75 | * List of actions.
76 | *
77 | * @return List of actions
78 | */
79 | public List getAgents() {
80 | List agentList = new ArrayList<>();
81 | for (Node node : Jenkins.get().getNodes()) {
82 | Computer computer = node.toComputer();
83 | if (computer instanceof SlaveComputer) {
84 | MaintenanceAction action = new MaintenanceAction((SlaveComputer) computer);
85 | if (action.hasMaintenanceWindows()) {
86 | agentList.add(action);
87 | }
88 | }
89 | }
90 |
91 | return agentList;
92 | }
93 |
94 | private void setError(Throwable error) {
95 | this.error = error;
96 | }
97 |
98 | /**
99 | * The message of the last error that occured.
100 | *
101 | * @return error message
102 | */
103 | public String getError() {
104 | StringWriter message = new StringWriter();
105 | error.printStackTrace(new PrintWriter(message));
106 | error = null;
107 | return message.toString();
108 | }
109 |
110 | public boolean hasError() {
111 | return error != null;
112 | }
113 |
114 | @Override
115 | public Badge getBadge() {
116 | int active = 0;
117 | int total = 0;
118 | List mwList = getAgents();
119 | for (MaintenanceAction ma : mwList) {
120 | total++;
121 | if (ma.hasActiveMaintenanceWindows()) {
122 | active++;
123 | }
124 | }
125 | if (total == 0) {
126 | return null;
127 | }
128 | String text = active + "/" + total;
129 | String tooltip = active + getVerb(active) + " an active maintenance window.\n"
130 | + total + getVerb(total) + " defined maintenance windows.";
131 | Badge.Severity severity = Badge.Severity.INFO;
132 | if (active > 0) {
133 | severity = Badge.Severity.WARNING;
134 | }
135 |
136 | return new Badge(text, tooltip, severity);
137 | }
138 |
139 | private String getVerb(int count) {
140 | if (count == 1) {
141 | return " agent has";
142 | }
143 | return " agents have";
144 | }
145 |
146 | /**
147 | * Delete given maintenance window.
148 | *
149 | * @param id The id of the maintenance to delete
150 | * @param computerName The name of the computer to which the maintenance belongs
151 | */
152 | @JavaScriptMethod
153 | public boolean deleteMaintenance(String id, String computerName) {
154 | if (hasPermission(computerName)) {
155 | try {
156 | MaintenanceHelper.getInstance().deleteMaintenanceWindow(computerName, id);
157 | return true;
158 | } catch (Throwable e) {
159 | LOGGER.log(Level.WARNING, "Error while deleting maintenance window", e);
160 | return false;
161 | }
162 | }
163 | return false;
164 | }
165 |
166 | /**
167 | * Delete selected maintenance windows.
168 | *
169 | * @param json An json with maintenance ids to delete and corresponding computer names
170 | */
171 | @JavaScriptMethod
172 | public String[] deleteMultiple(JSONObject json) {
173 | Map mwList = (Map) JSONObject.toBean(json, Map.class);
174 | List deletedList = new ArrayList<>();
175 | for (Entry entry : mwList.entrySet()) {
176 | String computerName = entry.getValue();
177 | if (hasPermission(computerName)) {
178 | String id = entry.getKey();
179 | try {
180 | MaintenanceHelper.getInstance().deleteMaintenanceWindow(computerName, id);
181 | deletedList.add(id);
182 | } catch (Throwable e) {
183 | LOGGER.log(Level.WARNING, "Error while deleting maintenance window", e);
184 | }
185 | }
186 | }
187 | return deletedList.toArray(new String[0]);
188 | }
189 |
190 | /**
191 | * UI method to fetch status about maintenance windows.
192 | *
193 | * @return A Map containing for each maintenance window whether it is active or not.
194 | */
195 | @JavaScriptMethod
196 | public Map getMaintenanceStatus() {
197 | Map statusList = new HashMap<>();
198 | for (MaintenanceAction action : getAgents()) {
199 | Computer computer = action.getComputer();
200 | if (computer.hasAnyPermission(Computer.DISCONNECT, Computer.CONFIGURE, Computer.EXTENDED_READ)) {
201 | try {
202 | for (MaintenanceWindow mw : MaintenanceHelper.getInstance().getMaintenanceWindows(computer.getName())) {
203 | if (!mw.isMaintenanceOver()) {
204 | statusList.put(mw.getId(), mw.isMaintenanceScheduled());
205 | }
206 | }
207 | } catch (IOException ioe) {
208 | LOGGER.log(Level.WARNING, "Failed to read maintenance windows", ioe);
209 | }
210 | }
211 | }
212 | return statusList;
213 | }
214 |
215 | private boolean hasPermission(String computerName) {
216 | Computer c = Jenkins.get().getComputer(computerName);
217 | if (c != null) {
218 | return c.hasAnyPermission(MaintenanceAction.CONFIGURE_AND_DISCONNECT);
219 | }
220 | return false;
221 | }
222 |
223 | @Restricted(NoExternalUse.class)
224 | public FormValidation doCheckLabel(@QueryParameter String value) {
225 | return LabelExpression.validate(value);
226 | }
227 |
228 | @Restricted(NoExternalUse.class)
229 | public AutoCompletionCandidates doAutoCompleteLabel(@QueryParameter String value) {
230 | return LabelExpression.autoComplete(value);
231 | }
232 |
233 | /**
234 | * Add a maintenance window to a list of machines.
235 | *
236 | * @param req StaplerRequest2
237 | * @param rsp StaplerResponse2
238 | * @throws IOException when saving xml failed
239 | * @throws ServletException when reading the form failed
240 | */
241 | @POST
242 | public void doAdd(StaplerRequest2 req, StaplerResponse2 rsp) throws IOException, ServletException {
243 | Jenkins j = Jenkins.get();
244 |
245 | JSONObject src = req.getSubmittedForm();
246 | String labelString = src.optString("label");
247 | Label label = j.getLabel(labelString);
248 | if (label != null) {
249 | Set nodes = label.getNodes();
250 | MaintenanceWindow mw = req.bindJSON(MaintenanceWindow.class, src);
251 | LOGGER.log(Level.FINER, "Adding maintenance windows {0}", mw);
252 | LOGGER.log(Level.FINER, "Adding maintenance windows for agents: {0}", nodes);
253 |
254 | nodes.stream()
255 | .filter(n -> n.toComputer() instanceof SlaveComputer && !(n.toComputer() instanceof AbstractCloudComputer)
256 | && Objects.requireNonNull(n.toComputer()).getRetentionStrategy() instanceof AgentMaintenanceRetentionStrategy
257 | && n.hasAnyPermission(MaintenanceAction.CONFIGURE_AND_DISCONNECT))
258 | .forEach(n -> {
259 | try {
260 | SlaveComputer computer = (SlaveComputer) n.toComputer();
261 | MaintenanceWindow maintenanceWindow = req.bindJSON(MaintenanceWindow.class, src);
262 | MaintenanceHelper.getInstance().addMaintenanceWindow(computer.getName(), maintenanceWindow);
263 | } catch (Exception e) {
264 | LOGGER.log(Level.WARNING, "Error while adding maintenance window", e);
265 | setError(e);
266 | }
267 | });
268 | }
269 | rsp.sendRedirect(".");
270 | }
271 |
272 | public Class getMaintenanceWindowClass() {
273 | return MaintenanceWindow.class;
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/LICENSES/Apache-2.0.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
10 |
11 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
12 |
13 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
14 |
15 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
16 |
17 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
18 |
19 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
20 |
21 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
22 |
23 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
24 |
25 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
26 |
27 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
28 |
29 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
30 |
31 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
32 |
33 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
34 |
35 | (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
36 |
37 | (b) You must cause any modified files to carry prominent notices stating that You changed the files; and
38 |
39 | (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
40 |
41 | (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
42 |
43 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
44 |
45 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
46 |
47 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
48 |
49 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
50 |
51 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
52 |
53 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
54 |
55 | END OF TERMS AND CONDITIONS
56 |
57 | APPENDIX: How to apply the Apache License to your work.
58 |
59 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
60 |
61 | Copyright 2022 SAP SE or an SAP affiliate company and agent-maintenance-plugin contributors
62 |
63 | Licensed under the Apache License, Version 2.0 (the "License");
64 | you may not use this file except in compliance with the License.
65 | You may obtain a copy of the License at
66 |
67 | http://www.apache.org/licenses/LICENSE-2.0
68 |
69 | Unless required by applicable law or agreed to in writing, software
70 | distributed under the License is distributed on an "AS IS" BASIS,
71 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
72 | See the License for the specific language governing permissions and
73 | limitations under the License.
74 |
--------------------------------------------------------------------------------
/src/main/resources/com/sap/prd/jenkins/plugins/agent_maintenance/MaintenanceAction/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |