Skip to content

Commit 722cdc2

Browse files
committed
feat: add group and batch patching
1 parent 9a97efa commit 722cdc2

6 files changed

Lines changed: 162 additions & 3 deletions

File tree

lib/facter/os_patching.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,5 +302,21 @@
302302
data['blocked_reasons'] = blocked_reasons
303303
data
304304
end
305+
306+
chunk(:group) do
307+
data = {}
308+
groupfile = os_patching_dir + '/group'
309+
if File.file?(groupfile)
310+
group = File.open(groupfile, 'r').to_a
311+
line = group.last.chomp
312+
matchdata = line.match(/^(.*)$/)
313+
if matchdata[0]
314+
data['group'] = matchdata[0]
315+
end
316+
else
317+
data['group'] = 'default'
318+
end
319+
data
320+
end
305321
end
306322
end

manifests/init.pp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@
9696
# @param ensure
9797
# `present` to install scripts, cronjobs, files, etc, `absent` to cleanup a system that previously hosted us
9898
#
99+
# @param group
100+
# The group to assign the node for patching purposes.
101+
#
99102
# @example assign node to 'Week3' patching window, force a reboot and create a blackout window for the end of the year
100103
# class { 'os_patching':
101104
# patch_window => 'Week3',
@@ -106,6 +109,7 @@
106109
# 'end' => '2019-01-15T23:59:59+10:00',
107110
# },
108111
# },
112+
#. group => 'patching01',
109113
# }
110114
#
111115
# @example An example profile to setup patching, sourcing blackout windows from hiera
@@ -125,6 +129,7 @@
125129
# patch_window => $patch_window,
126130
# reboot_override => $reboot_override,
127131
# blackout_windows => $full_blackout_windows,
132+
# group => 'patching01',
128133
# }
129134
# }
130135
#
@@ -165,6 +170,7 @@
165170
Integer[0,59] $patch_cron_min = fqdn_rand(59),
166171
Optional[String] $patch_window = undef,
167172
Optional[Hash] $blackout_windows = undef,
173+
Optional[String[1]] $group = undef,
168174
) {
169175
# None tunable
170176
$cache_dir = lookup('os_patching::cache_dir',Stdlib::Absolutepath,first,undef)
@@ -211,6 +217,10 @@
211217
fail('The patch window can only contain alphanumerics, space, underscore and dash')
212218
}
213219

220+
if ($group and $group !~ /^[A-Za-z0-9\-_ ]+$/ ) {
221+
fail('The group can only contain alphanumerics, space, underscore and dash')
222+
}
223+
214224
file { $cache_dir:
215225
ensure => $ensure_dir,
216226
force => true,
@@ -243,6 +253,11 @@
243253
default => 'absent'
244254
}
245255

256+
$group_ensure = ($ensure == 'present' and $group) ? {
257+
true => 'file',
258+
default => 'absent',
259+
}
260+
246261
file { "${cache_dir}/patch_window":
247262
ensure => $patch_window_ensure,
248263
content => $patch_window,
@@ -258,6 +273,11 @@
258273
notify => Exec[$fact_exec],
259274
}
260275

276+
file { "${cache_dir}/group":
277+
ensure => $group_ensure,
278+
content => $group,
279+
}
280+
261281
$reboot_override_ensure = ($ensure == 'present' and $reboot_override) ? {
262282
true => 'file',
263283
default => 'absent',
@@ -314,6 +334,7 @@
314334
"${cache_dir}/patch_window",
315335
"${cache_dir}/reboot_override",
316336
"${cache_dir}/blackout_windows",
337+
"${cache_dir}/group",
317338
],
318339
}
319340
}

plans/patch_after_healthcheck.pp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@
99
) {
1010
# Run an initial health check to make sure the target nodes are ready
1111

12-
$health_checks = run_task('puppet_health_check::agent_health',
13-
$nodes,
12+
$health_checks = run_task('puppet_health_check::agent_health', $nodes,
1413
target_noop_state => $noop_state,
1514
target_service_enabled => true,
1615
target_service_running => true,
1716
target_runinterval => $runinterval,
18-
'_catch_errors' => true,
17+
_catch_errors => true,
1918
)
2019

2120
$nodes_to_patch = $health_checks.filter | $items | { $items.value['state'] == 'clean' }

plans/patch_batch.pp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# @summary Patch nodes in a batch
2+
#
3+
plan os_patching::patch_batch (
4+
Array $batch = [],
5+
Boolean $catch_errors = true,
6+
Boolean $noop_state = false,
7+
Boolean $run_health_check = false,
8+
Boolean $service_enabled = true,
9+
Boolean $service_running = true,
10+
Integer $runinterval = 1800,
11+
) {
12+
out::message("patch_batch.pp: Patching batch of nodes: ${batch}")
13+
14+
$targets = get_targets($batch)
15+
16+
if $run_health_check {
17+
out::message('patch_batch.pp: Running health check before patching')
18+
run_task('os_patching::health_check', $targets,
19+
_catch_errors => $catch_errors,
20+
target_noop_state => $noop_state,
21+
target_runinterval => $runinterval,
22+
target_service_enabled => $service_enabled,
23+
target_service_running => $service_running,
24+
)
25+
26+
$nodes_to_patch = $health_checks.filter | $items | { $items.value['state'] == 'clean' }
27+
$nodes_skipped = $health_checks.filter | $items | { $items.value['state'] != 'clean' }
28+
29+
$skipped_nodes = $nodes_skipped.map | $value | { $value['certname'] }
30+
$patchable_nodes = $nodes_to_patch.map | $value | { $value['certname'] }
31+
32+
$task_result = run_task('os_patching::patch_server', $patchable_nodes,
33+
_catch_errors => $catch_errors,
34+
)
35+
36+
$successful_patched_nodes = $task_result.ok_set.names
37+
$failed_patched_nodes = $task_result.error_set.names
38+
} else {
39+
out::message('patch_batch.pp: Health check is disabled.')
40+
$task_result = run_task('os_patching::patch_server', $targets,
41+
_catch_errors => $catch_errors,
42+
)
43+
44+
log::debug("patch_batch.pp: Patching task result for ${targets}: ${task_result}")
45+
46+
$successful_patched_nodes = $task_result.ok_set.names
47+
$failed_patched_nodes = $task_result.error_set.names
48+
$skipped_nodes = [] # No skipped nodes if health check is not run
49+
}
50+
51+
return(
52+
{
53+
targets => $targets,
54+
patched => $successful_patched_nodes,
55+
failed => $failed_patched_nodes,
56+
skipped => $skipped_nodes,
57+
health_check => $run_health_check,
58+
}
59+
)
60+
}

plans/patch_group.pp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# @summary Patch nodes collected by a fact group
2+
#
3+
plan os_patching::patch_group (
4+
String[1] $group,
5+
Boolean $patch_in_batches = true,
6+
Integer $batch_size = 15,
7+
) {
8+
$pql_query = puppetdb_query("inventory[certname] { facts.os_patching.group = '${group}'}")
9+
$certnames = $pql_query.map |$item| { $item['certname'] }
10+
$targets = get_targets($certnames)
11+
12+
out::message("patch_group.pp: Patching group: ${group}")
13+
out::message("patch_group.pp: Targets in group: ${targets}")
14+
15+
if $patch_in_batches {
16+
out::message('patch_group.pp: Patching in batches is enabled')
17+
out::message("patch_group.pp: Patching in batches of size: ${batch_size}")
18+
19+
$batches = slice($targets, $batch_size)
20+
out::message("patch_group.pp: Patching batches created: ${batches}")
21+
22+
$batch_results = $batches.map |$batch| {
23+
# out::message("patch_group.pp: Patching with nodes: ${batch}")
24+
run_plan('os_patching::patch_batch', { batch => $batch })
25+
}
26+
27+
# Merge all batch results into a single hash by combining arrays
28+
$result = {
29+
'targets' => $batch_results.map |$r| { $r['targets'] }.flatten,
30+
'patched' => $batch_results.map |$r| { $r['patched'] }.flatten,
31+
'failed' => $batch_results.map |$r| { $r['failed'] }.flatten,
32+
'skipped' => $batch_results.map |$r| { $r['skipped'] }.flatten,
33+
'health_check' => $batch_results[0]['health_check'],
34+
}
35+
} else {
36+
out::message('patch_group.pp: Patching in batches is disabled')
37+
out::message("patch_group.pp: Patching all targets at once: ${targets}")
38+
$result = run_plan('os_patching::patch_batch', { batch => $targets })
39+
}
40+
41+
return $result
42+
}

plans/patch_pql.pp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# @summary Patch nodes collected by a PQL query
2+
#
3+
plan os_patching::patch_pql (
4+
String[1] $pql_query = 'inventory[certname] { facts.os.family = "redhat" }',
5+
Boolean $patch_in_batches = true,
6+
Integer $batch_size = 15,
7+
) {
8+
$pql_query = puppetdb_query($pql_query)
9+
$certnames = $pql_query.map |$item| { $item['certname'] }
10+
$targets = get_targets($certnames)
11+
12+
if $patch_in_batches {
13+
$batches = slice($targets, $batch_size)
14+
15+
$batches.each |$batch| {
16+
$result = run_plan('os_patching::patch_batch', { batch => $batch })
17+
}
18+
} else {
19+
$result = run_plan('os_patching::patch_batch', { batch => $targets })
20+
}
21+
}

0 commit comments

Comments
 (0)