Skip to content

Commit d416c9c

Browse files
authored
Business Rule: Enforce CI maintenance window on Change schedule - Pull 1 (#2530)
* Add README for enforcing CI maintenance window rules This README provides details on enforcing CI maintenance windows for change requests, including configuration options and references to relevant APIs. * Create business rule to enforce CI maintenance window
1 parent 6217e55 commit d416c9c

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Enforce CI maintenance window on Change schedule
2+
3+
## What this solves
4+
Change requests are sometimes scheduled outside the maintenance windows of the affected CIs, causing risky or blocked implementations. This rule validates the planned start and end times of a Change against the maintenance schedules of its related CIs and blocks the update if none of the CIs allow that window.
5+
6+
## Where to use
7+
- Table: `change_request`
8+
- When: before insert and before update
9+
- Order: early (for example 50)
10+
11+
## How it works
12+
- Looks up CIs related to the Change via `task_ci`
13+
- For each CI with a defined `maintenance_schedule` (reference to `cmn_schedule`), uses `GlideSchedule.isInSchedule` to verify the planned start and end are inside the window
14+
- If at least one CI’s maintenance schedule permits the window, the Change is allowed
15+
- If no related CI permits the window, the rule aborts the action with a clear message
16+
- Behaviour for CIs without a defined maintenance schedule is configurable
17+
18+
## Configure
19+
At the top of the script:
20+
- `BLOCK_WHEN_NO_SCHEDULE`: if true, treat CIs without a maintenance schedule as non-compliant
21+
- `REQUIRE_BOTH_BOUNDARIES`: if true, both planned start and planned end must be inside a permitted window
22+
- `TIMEZONE`: optional IANA time zone string (for example `Europe/London`); leave blank to use the schedule or instance default
23+
24+
## Notes
25+
- If your process requires all CIs to permit the window, change `anyPass` logic to `allPass`
26+
- This rule checks only maintenance windows defined on the CI record. If you store schedules at the Business Service level, adapt the CI lookup accordingly
27+
28+
## References
29+
- GlideSchedule API
30+
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideSchedule/concept/c_GlideScheduleAPI.html
31+
- GlideDateTime API
32+
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideDateTime/concept/c_GlideDateTimeAPI.html
33+
- Change Management fields
34+
https://www.servicenow.com/docs/bundle/zurich-it-service-management/page/product/change-management/concept/change-management-overview.html
35+
- Task CI relationship (`task_ci`)
36+
https://www.servicenow.com/docs/bundle/zurich-servicenow-platform/page/product/configuration-management/reference/task-ci.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Business Rule: Enforce CI maintenance window on Change schedule
2+
// Table: change_request | When: before insert, before update
3+
4+
(function executeRule(current, previous /*null*/) {
5+
// ===== Configuration =====
6+
var BLOCK_WHEN_NO_SCHEDULE = false; // if true, a CI without maintenance_schedule causes failure
7+
var REQUIRE_BOTH_BOUNDARIES = true; // if true, both planned start and end must be inside maintenance window
8+
var TIMEZONE = 'Europe/London'; // optional; '' to use schedule/instance default
9+
// =========================
10+
11+
try {
12+
// Only run when dates are meaningful
13+
if (!current.planned_start_date || !current.planned_end_date) return;
14+
15+
// Build GDTs once
16+
var psd = new GlideDateTime(current.planned_start_date.getDisplayValue());
17+
var ped = new GlideDateTime(current.planned_end_date.getDisplayValue());
18+
if (psd.after(ped)) {
19+
gs.addErrorMessage('Planned start is after planned end. Please correct the schedule.');
20+
current.setAbortAction(true);
21+
return;
22+
}
23+
24+
// Collect related CIs for this Change
25+
var ciIds = [];
26+
var tci = new GlideRecord('task_ci');
27+
tci.addQuery('task', current.getUniqueValue());
28+
tci.query();
29+
while (tci.next()) ciIds.push(String(tci.getValue('ci_item')));
30+
31+
if (ciIds.length === 0) {
32+
// No CIs; nothing to validate
33+
return;
34+
}
35+
36+
var anyPass = false;
37+
var missingScheduleCount = 0;
38+
var evaluated = 0;
39+
40+
// Evaluate each CI's maintenance schedule
41+
var ci = new GlideRecord('cmdb_ci');
42+
ci.addQuery('sys_id', 'IN', ciIds.join(','));
43+
ci.query();
44+
45+
while (ci.next()) {
46+
evaluated++;
47+
48+
var schedRef = ci.getValue('maintenance_schedule');
49+
if (!schedRef) {
50+
missingScheduleCount++;
51+
continue;
52+
}
53+
54+
var sched = new GlideSchedule(schedRef, TIMEZONE || '');
55+
var startOK = sched.isInSchedule(psd);
56+
var endOK = sched.isInSchedule(ped);
57+
58+
var pass = REQUIRE_BOTH_BOUNDARIES ? (startOK && endOK) : (startOK || endOK);
59+
if (pass) {
60+
anyPass = true;
61+
break; // at least one CI permits this window
62+
}
63+
}
64+
65+
// Handle missing schedules according to policy
66+
if (!anyPass) {
67+
var hasBlockingNoSchedule = BLOCK_WHEN_NO_SCHEDULE && missingScheduleCount > 0;
68+
if (hasBlockingNoSchedule || evaluated > 0) {
69+
gs.addErrorMessage(buildMessage());
70+
current.setAbortAction(true);
71+
}
72+
}
73+
74+
function buildMessage() {
75+
var parts = [];
76+
parts.push('Planned window does not fall inside any related CI maintenance schedules.');
77+
if (REQUIRE_BOTH_BOUNDARIES) parts.push('Both start and end must be inside a permitted window.');
78+
if (missingScheduleCount > 0) {
79+
parts.push((BLOCK_WHEN_NO_SCHEDULE ? 'Blocking' : 'Ignoring') + ' ' + missingScheduleCount + ' CI(s) with no maintenance schedule.');
80+
}
81+
return parts.join(' ');
82+
}
83+
} catch (e) {
84+
gs.error('Maintenance window validation failed: ' + e.message);
85+
// Be safe: do not block due to a runtime error
86+
}
87+
})(current, previous);

0 commit comments

Comments
 (0)