diff --git a/REFERENCE.md b/REFERENCE.md
index e7563f9c..c4454500 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -74,6 +74,7 @@
* [`infrastatus`](#infrastatus): Runs puppet infra status and returns the output
* [`mkdir_p_file`](#mkdir_p_file): Create a file with the specified content at the specified location
* [`mv`](#mv): Wrapper task for mv command
+* [`node_group_unpin`](#node_group_unpin): Unpins nodes from a specified PE node group
* [`os_identification`](#os_identification): Return the operating system runnin gon the target as a string
* [`pe_install`](#pe_install): Install Puppet Enterprise from a tarball
* [`pe_ldap_config`](#pe_ldap_config): Set the ldap config in the PE console
@@ -1324,6 +1325,26 @@ Data type: `String`
New path of file
+### `node_group_unpin`
+
+Unpins nodes from a specified PE node group
+
+**Supports noop?** false
+
+#### Parameters
+
+##### `node_certnames`
+
+Data type: `Array[String]`
+
+The certnames of the nodes to unpin
+
+##### `group_name`
+
+Data type: `String`
+
+The name of the node group to unpin the nodes from
+
### `os_identification`
Return the operating system runnin gon the target as a string
diff --git a/manifests/setup/legacy_compiler_group.pp b/manifests/setup/legacy_compiler_group.pp
index e3601200..5534ae49 100644
--- a/manifests/setup/legacy_compiler_group.pp
+++ b/manifests/setup/legacy_compiler_group.pp
@@ -1,23 +1,23 @@
# @api private
class peadm::setup::legacy_compiler_group (
String[1] $primary_host,
- Optional[String] $internal_compiler_a_pool_address = undef,
- Optional[String] $internal_compiler_b_pool_address = undef,
+ Optional[String] $internal_compiler_a_pool_address = undef,
+ Optional[String] $internal_compiler_b_pool_address = undef,
) {
Node_group {
purge_behavior => none,
}
node_group { 'PE Legacy Compiler':
- parent => 'PE Master',
- rule => ['and',
- ['=', ['trusted', 'extensions', peadm::oid('peadm_legacy_compiler')], 'true'],
- ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler'],
- ],
+ ensure => 'present',
+ parent => 'PE Infrastructure',
+ rule => ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler_legacy'],
classes => {
- 'puppet_enterprise::profile::master' => {
- 'puppetdb_host' => [$internal_compiler_a_pool_address, $internal_compiler_b_pool_address].filter |$_| { $_ },
- 'puppetdb_port' => [8081],
+ 'puppet_enterprise::profile::master' => {
+ 'puppetdb_host' => [$internal_compiler_a_pool_address, $internal_compiler_b_pool_address].filter |$_| { $_ },
+ 'puppetdb_port' => [8081],
+ 'replication_mode' => 'none',
+ 'code_manager_auto_configure' => true,
},
},
}
@@ -26,18 +26,16 @@
ensure => 'present',
parent => 'PE Legacy Compiler',
rule => ['and',
- ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler'],
+ ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler_legacy'],
['=', ['trusted', 'extensions', peadm::oid('peadm_availability_group')], 'A'],
- ['=', ['trusted', 'extensions', peadm::oid('peadm_legacy_compiler')], 'true'],
],
classes => {
- 'puppet_enterprise::profile::master' => {
+ 'puppet_enterprise::profile::master' => {
'puppetdb_host' => [$internal_compiler_b_pool_address, $internal_compiler_a_pool_address].filter |$_| { $_ },
'puppetdb_port' => [8081],
},
},
data => {
- # Workaround for GH-118
'puppet_enterprise::profile::master::puppetdb' => {
'ha_enabled_replicas' => [],
},
@@ -45,21 +43,20 @@
}
node_group { 'PE Legacy Compiler Group B':
- ensure => 'present',
- parent => 'PE Legacy Compiler',
- rule => ['and',
- ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler'],
+ ensure => 'present',
+ parent => 'PE Legacy Compiler',
+ purge_behavior => 'classes',
+ rule => ['and',
+ ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler_legacy'],
['=', ['trusted', 'extensions', peadm::oid('peadm_availability_group')], 'B'],
- ['=', ['trusted', 'extensions', peadm::oid('peadm_legacy_compiler')], 'true'],
],
- classes => {
- 'puppet_enterprise::profile::master' => {
+ classes => {
+ 'puppet_enterprise::profile::master' => {
'puppetdb_host' => [$internal_compiler_a_pool_address, $internal_compiler_b_pool_address].filter |$_| { $_ },
'puppetdb_port' => [8081],
},
},
- data => {
- # Workaround for GH-118
+ data => {
'puppet_enterprise::profile::master::puppetdb' => {
'ha_enabled_replicas' => [],
},
@@ -67,6 +64,6 @@
}
node_group { 'PE Compiler':
- rule => ['and', ['=', ['trusted', 'extensions', peadm::oid('peadm_legacy_compiler')], 'false']],
+ rule => ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler'],
}
}
diff --git a/manifests/setup/node_manager.pp b/manifests/setup/node_manager.pp
index f74cb217..fad61950 100644
--- a/manifests/setup/node_manager.pp
+++ b/manifests/setup/node_manager.pp
@@ -82,7 +82,7 @@
# PE Compiler group comes from default PE and already has the pe compiler role
node_group { 'PE Compiler':
parent => 'PE Master',
- rule => ['and', ['=', ['trusted', 'extensions', peadm::oid('peadm_legacy_compiler')], 'false']],
+ rule => ['and', ['=', ['trusted', 'extensions', peadm::oid('pp_auth_role')], 'pe_compiler']],
}
# This group should pin the primary, and also map to any pe-postgresql nodes
@@ -121,7 +121,6 @@
rule => ['and',
['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler'],
['=', ['trusted', 'extensions', peadm::oid('peadm_availability_group')], 'A'],
- ['=', ['trusted', 'extensions', peadm::oid('peadm_legacy_compiler')], 'false'],
],
classes => {
'puppet_enterprise::profile::puppetdb' => {
@@ -180,7 +179,6 @@
rule => ['and',
['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler'],
['=', ['trusted', 'extensions', peadm::oid('peadm_availability_group')], 'B'],
- ['=', ['trusted', 'extensions', peadm::oid('peadm_legacy_compiler')], 'false'],
],
classes => {
'puppet_enterprise::profile::puppetdb' => {
@@ -203,10 +201,7 @@
node_group { 'PE Legacy Compiler':
parent => 'PE Master',
- rule => ['and',
- ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler'],
- ['=', ['trusted', 'extensions', peadm::oid('peadm_legacy_compiler')], 'true'],
- ],
+ rule => ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler_legacy'],
classes => {
'puppet_enterprise::profile::master' => {
'puppetdb_host' => [$internal_compiler_a_pool_address, $internal_compiler_b_pool_address].filter |$_| { $_ },
@@ -221,9 +216,8 @@
ensure => 'present',
parent => 'PE Legacy Compiler',
rule => ['and',
- ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler'],
+ ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler_legacy'],
['=', ['trusted', 'extensions', peadm::oid('peadm_availability_group')], 'A'],
- ['=', ['trusted', 'extensions', peadm::oid('peadm_legacy_compiler')], 'true'],
],
classes => {
'puppet_enterprise::profile::master' => {
@@ -245,9 +239,8 @@
ensure => 'present',
parent => 'PE Legacy Compiler',
rule => ['and',
- ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler'],
+ ['=', ['trusted', 'extensions', 'pp_auth_role'], 'pe_compiler_legacy'],
['=', ['trusted', 'extensions', peadm::oid('peadm_availability_group')], 'B'],
- ['=', ['trusted', 'extensions', peadm::oid('peadm_legacy_compiler')], 'true'],
],
classes => {
'puppet_enterprise::profile::master' => {
diff --git a/plans/convert.pp b/plans/convert.pp
index 1ff1771a..ef431dc3 100644
--- a/plans/convert.pp
+++ b/plans/convert.pp
@@ -214,7 +214,6 @@
add_extensions => {
peadm::oid('pp_auth_role') => 'pe_compiler',
peadm::oid('peadm_availability_group') => 'A',
- peadm::oid('peadm_legacy_compiler') => 'false',
},
)
},
@@ -224,7 +223,6 @@
add_extensions => {
peadm::oid('pp_auth_role') => 'pe_compiler',
peadm::oid('peadm_availability_group') => 'B',
- peadm::oid('peadm_legacy_compiler') => 'false',
},
)
},
@@ -232,9 +230,8 @@
run_plan('peadm::modify_certificate', $legacy_compiler_a_targets,
primary_host => $primary_target,
add_extensions => {
- peadm::oid('pp_auth_role') => 'pe_compiler',
+ peadm::oid('pp_auth_role') => 'pe_compiler_legacy',
peadm::oid('peadm_availability_group') => 'A',
- peadm::oid('peadm_legacy_compiler') => 'true',
},
)
},
@@ -242,9 +239,8 @@
run_plan('peadm::modify_certificate', $legacy_compiler_b_targets,
primary_host => $primary_target,
add_extensions => {
- peadm::oid('pp_auth_role') => 'pe_compiler',
+ peadm::oid('pp_auth_role') => 'pe_compiler_legacy',
peadm::oid('peadm_availability_group') => 'B',
- peadm::oid('peadm_legacy_compiler') => 'true',
},
)
},
@@ -283,6 +279,14 @@
include peadm::setup::convert_node_manager
}
+
+ # Unpin legacy compilers from PE Master group
+ if $legacy_compiler_targets {
+ run_task('peadm::node_group_unpin', $primary_target,
+ node_certnames => $legacy_compiler_targets.map |$target| { $target.peadm::certname() },
+ group_name => 'PE Master',
+ )
+ }
}
else {
# lint:ignore:strict_indent
@@ -329,5 +333,7 @@
# lint:endignore
}
+ run_task('peadm::update_pe_master_rules', $primary_target)
+
return("Conversion to peadm Puppet Enterprise ${arch['architecture']} completed.")
}
diff --git a/plans/convert_compiler_to_legacy.pp b/plans/convert_compiler_to_legacy.pp
index c75924bd..2f662013 100644
--- a/plans/convert_compiler_to_legacy.pp
+++ b/plans/convert_compiler_to_legacy.pp
@@ -102,7 +102,7 @@
run_plan('peadm::modify_certificate', $compiler_targets,
primary_host => $primary_target,
add_extensions => {
- peadm::oid('peadm_legacy_compiler') => 'false',
+ peadm::oid('pp_auth_role') => 'pe_compiler_legacy',
},
)
},
@@ -110,9 +110,8 @@
run_plan('peadm::modify_certificate', $legacy_compiler_a_targets,
primary_host => $primary_target,
add_extensions => {
- peadm::oid('pp_auth_role') => 'pe_compiler',
+ peadm::oid('pp_auth_role') => 'pe_compiler_legacy',
peadm::oid('peadm_availability_group') => 'A',
- peadm::oid('peadm_legacy_compiler') => 'true',
},
)
},
@@ -120,9 +119,8 @@
run_plan('peadm::modify_certificate', $legacy_compiler_b_targets,
primary_host => $primary_target,
add_extensions => {
- peadm::oid('pp_auth_role') => 'pe_compiler',
+ peadm::oid('pp_auth_role') => 'pe_compiler_legacy',
peadm::oid('peadm_availability_group') => 'B',
- peadm::oid('peadm_legacy_compiler') => 'true',
},
)
},
diff --git a/plans/install.pp b/plans/install.pp
index 4e55e0be..7ce03cf0 100644
--- a/plans/install.pp
+++ b/plans/install.pp
@@ -143,6 +143,8 @@
final_agent_state => $final_agent_state,
)
+ run_task('peadm::update_pe_master_rules', $primary_host)
+
# Return a string banner reporting on what was done
return([$install_result, $configure_result])
}
diff --git a/plans/subplans/component_install.pp b/plans/subplans/component_install.pp
index c117ccb6..2fad74e8 100644
--- a/plans/subplans/component_install.pp
+++ b/plans/subplans/component_install.pp
@@ -21,13 +21,11 @@
$certificate_extensions = {
peadm::oid('pp_auth_role') => 'pe_compiler',
peadm::oid('peadm_availability_group') => $avail_group_letter,
- peadm::oid('peadm_legacy_compiler') => false,
}
} elsif $role == 'pe_compiler_legacy' {
$certificate_extensions = {
- peadm::oid('pp_auth_role') => 'pe_compiler',
+ peadm::oid('pp_auth_role') => 'pe_compiler_legacy',
peadm::oid('peadm_availability_group') => $avail_group_letter,
- peadm::oid('peadm_legacy_compiler') => true,
}
} else {
$certificate_extensions = {
diff --git a/plans/subplans/install.pp b/plans/subplans/install.pp
index 693c056c..96af47f0 100644
--- a/plans/subplans/install.pp
+++ b/plans/subplans/install.pp
@@ -287,7 +287,6 @@
extension_requests => {
peadm::oid('pp_auth_role') => 'pe_compiler',
peadm::oid('peadm_availability_group') => 'A',
- peadm::oid('peadm_legacy_compiler') => 'false',
}
)
},
@@ -296,25 +295,22 @@
extension_requests => {
peadm::oid('pp_auth_role') => 'pe_compiler',
peadm::oid('peadm_availability_group') => 'B',
- peadm::oid('peadm_legacy_compiler') => 'false',
}
)
},
background('compiler-a-csr.yaml') || {
run_plan('peadm::util::insert_csr_extension_requests', $legacy_a_targets,
extension_requests => {
- peadm::oid('pp_auth_role') => 'pe_compiler',
+ peadm::oid('pp_auth_role') => 'pe_compiler_legacy',
peadm::oid('peadm_availability_group') => 'A',
- peadm::oid('peadm_legacy_compiler') => 'true',
}
)
},
background('compiler-b-csr.yaml') || {
run_plan('peadm::util::insert_csr_extension_requests', $legacy_b_targets,
extension_requests => {
- peadm::oid('pp_auth_role') => 'pe_compiler',
+ peadm::oid('pp_auth_role') => 'pe_compiler_legacy',
peadm::oid('peadm_availability_group') => 'B',
- peadm::oid('peadm_legacy_compiler') => 'true',
}
)
},
diff --git a/plans/update_compiler_extensions.pp b/plans/update_compiler_extensions.pp
index 784f919e..ff17e89f 100644
--- a/plans/update_compiler_extensions.pp
+++ b/plans/update_compiler_extensions.pp
@@ -7,11 +7,6 @@
$primary_target = peadm::get_targets($primary_host, 1)
$host_targets = peadm::get_targets($compiler_hosts)
- run_plan('peadm::modify_certificate', $host_targets,
- primary_host => $primary_target,
- add_extensions => { peadm::oid('peadm_legacy_compiler') => String($legacy) },
- )
-
run_task('peadm::puppet_runonce', $primary_target)
run_task('peadm::puppet_runonce', $host_targets)
diff --git a/plans/upgrade.pp b/plans/upgrade.pp
index 15b240fe..a591bb5f 100644
--- a/plans/upgrade.pp
+++ b/plans/upgrade.pp
@@ -135,6 +135,26 @@
peadm::assert_supported_pe_version($_version, $permit_unsafe_versions)
+ # Gather certificate extension information from all systems
+ $cert_extensions_temp = run_task('peadm::cert_data', $all_targets).reduce({}) |$memo,$result| {
+ $memo + { $result.target.peadm::certname => $result['extensions'] }
+ }
+
+ # Add legacy compiler role to compilers that are missing it
+ $legacy_compiler_targets = $cert_extensions_temp.filter |$name,$exts| {
+ ($name in $compiler_targets.map |$t| { $t.name }) and
+ ($exts[peadm::oid('peadm_legacy_compiler')] != undef) and
+ ($exts[peadm::oid('peadm_legacy_compiler')] == 'true') and
+ ($exts['pp_auth_role'] != 'pe_compiler_legacy')
+ }.keys
+
+ run_plan('peadm::modify_certificate', $legacy_compiler_targets,
+ primary_host => $primary_target,
+ add_extensions => {
+ 'pp_auth_role' => 'pe_compiler_legacy',
+ },
+ )
+
# Gather certificate extension information from all systems
$cert_extensions = run_task('peadm::cert_data', $all_targets).reduce({}) |$memo,$result| {
$memo + { $result.target.peadm::certname => $result['extensions'] }
@@ -172,8 +192,8 @@
$compiler_m1_nonlegacy_targets = $compiler_targets.filter |$target| {
($cert_extensions.dig($target.peadm::certname, peadm::oid('peadm_availability_group'))
== $cert_extensions.dig($primary_target[0].peadm::certname, peadm::oid('peadm_availability_group'))) and
- ($cert_extensions.dig($target.peadm::certname, peadm::oid('peadm_legacy_compiler'))
- == 'false')
+ ($cert_extensions.dig($target.peadm::certname, peadm::oid('pp_auth_role'))
+ == 'pe_compiler')
}
$compiler_m2_targets = $compiler_targets.filter |$target| {
@@ -184,8 +204,8 @@
$compiler_m2_nonlegacy_targets = $compiler_targets.filter |$target| {
($cert_extensions.dig($target.peadm::certname, peadm::oid('peadm_availability_group'))
== $cert_extensions.dig($replica_target[0].peadm::certname, peadm::oid('peadm_availability_group'))) and
- ($cert_extensions.dig($target.peadm::certname, peadm::oid('peadm_legacy_compiler'))
- == 'false')
+ ($cert_extensions.dig($target.peadm::certname, peadm::oid('pp_auth_role'))
+ == 'pe_compiler')
}
peadm::plan_step('preparation') || {
@@ -440,5 +460,7 @@
peadm::check_version_and_known_hosts($current_pe_version, $_version, $r10k_known_hosts)
+ run_task('peadm::update_pe_master_rules', $primary_target)
+
return("Upgrade of Puppet Enterprise ${arch['architecture']} completed.")
}
diff --git a/spec/plans/convert_spec.rb b/spec/plans/convert_spec.rb
index 39ec7367..161123cc 100644
--- a/spec/plans/convert_spec.rb
+++ b/spec/plans/convert_spec.rb
@@ -9,7 +9,7 @@
end
let(:params) do
- { 'primary_host' => 'primary' }
+ { 'primary_host' => 'primary', 'legacy_compilers' => ['pe_compiler_legacy'] }
end
it 'single primary no dr valid' do
@@ -21,6 +21,8 @@
expect_task('peadm::cert_data').return_for_targets('primary' => trustedjson)
expect_task('peadm::read_file').always_return({ 'content' => '2021.7.9' })
expect_task('peadm::get_group_rules').return_for_targets('primary' => { '_output' => '{"rules": []}' })
+ expect_task('peadm::node_group_unpin').with_targets('primary').with_params({ 'node_certnames' => ['pe_compiler_legacy'], 'group_name' => 'PE Master' })
+ expect_task('peadm::check_legacy_compilers').with_targets('primary').with_params({ 'legacy_compilers' => 'pe_compiler_legacy' }).return_for_targets('primary' => { '_output' => '' })
# For some reason, expect_plan() was not working??
allow_plan('peadm::modify_certificate').always_return({})
diff --git a/tasks/get_peadm_config.rb b/tasks/get_peadm_config.rb
index 9eb3aa02..4bfffd35 100755
--- a/tasks/get_peadm_config.rb
+++ b/tasks/get_peadm_config.rb
@@ -22,7 +22,7 @@ def execute!
def config
# Compute values
- primary = groups.pinned('PE Master')
+ primary = groups.pinned('PE Certificate Authority')
replica = groups.pinned('PE HA Replica')
server_a = server('puppet/server', 'A', [primary, replica].compact)
server_b = server('puppet/server', 'B', [primary, replica].compact)
@@ -94,8 +94,7 @@ def groups
def compilers
@compilers ||=
pdb_query('inventory[certname,trusted.extensions] {
- trusted.extensions.pp_auth_role = "pe_compiler" and
- trusted.extensions."1.3.6.1.4.1.34380.1.1.9814" = "false"
+ trusted.extensions.pp_auth_role = "pe_compiler"
}').map do |c|
{
'certname' => c['certname'],
@@ -108,8 +107,7 @@ def compilers
def legacy_compilers
@legacy_compilers ||=
pdb_query('inventory[certname,trusted.extensions] {
- trusted.extensions.pp_auth_role = "pe_compiler" and
- trusted.extensions."1.3.6.1.4.1.34380.1.1.9814" = "true"
+ trusted.extensions.pp_auth_role = "legacy_compiler"
}').map do |c|
{
'certname' => c['certname'],
diff --git a/tasks/node_group_unpin.json b/tasks/node_group_unpin.json
new file mode 100644
index 00000000..c94f3654
--- /dev/null
+++ b/tasks/node_group_unpin.json
@@ -0,0 +1,17 @@
+{
+ "description": "Unpins nodes from a specified PE node group",
+ "parameters": {
+ "node_certnames": {
+ "type": "Array[String]",
+ "description": "The certnames of the nodes to unpin"
+ },
+ "group_name": {
+ "type": "String",
+ "description": "The name of the node group to unpin the nodes from"
+ }
+ },
+ "input_method": "stdin",
+ "implementations": [
+ {"name": "node_group_unpin.rb"}
+ ]
+}
\ No newline at end of file
diff --git a/tasks/node_group_unpin.rb b/tasks/node_group_unpin.rb
new file mode 100755
index 00000000..2a2e5637
--- /dev/null
+++ b/tasks/node_group_unpin.rb
@@ -0,0 +1,118 @@
+#!/opt/puppetlabs/puppet/bin/ruby
+# frozen_string_literal: true
+
+require 'json'
+require 'yaml'
+require 'net/https'
+require 'puppet'
+
+# NodeGroupUnpin task class
+class NodeGroupUnpin
+ def initialize(params)
+ @params = params
+ raise "Missing required parameter 'node_certnames'" unless @params['node_certnames']
+ raise "'node_certnames' must be an array" unless @params['node_certnames'].is_a?(Array)
+ raise "Missing required parameter 'group_name'" unless @params['group_name']
+ @auth = YAML.load_file('/etc/puppetlabs/puppet/classifier.yaml')
+ rescue Errno::ENOENT
+ raise 'Could not find classifier.yaml at /etc/puppetlabs/puppet/classifier.yaml'
+ end
+
+ def https_client
+ client = Net::HTTP.new(Puppet.settings[:certname], 4433)
+ client.use_ssl = true
+ client.cert = @cert ||= OpenSSL::X509::Certificate.new(File.read(Puppet.settings[:hostcert]))
+ client.key = @key ||= OpenSSL::PKey::RSA.new(File.read(Puppet.settings[:hostprivkey]))
+ client.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ client.ca_file = Puppet.settings[:localcacert]
+ client
+ end
+
+ def groups
+ @groups ||= begin
+ net = https_client
+ res = net.get('/classifier-api/v1/groups')
+
+ unless res.code == '200'
+ raise "Failed to fetch groups: HTTP #{res.code} - #{res.body}"
+ end
+
+ NodeGroup.new(JSON.parse(res.body))
+ rescue JSON::ParserError => e
+ raise "Invalid JSON response from server: #{e.message}"
+ rescue StandardError => e
+ raise "Error fetching groups: #{e.message}"
+ end
+ end
+
+ def unpin_node(group, nodes)
+ raise 'Invalid group object' unless group.is_a?(Hash) && group['id'] && group['name']
+
+ net = https_client
+ begin
+ data = { "nodes": nodes }.to_json
+ url = "/classifier-api/v1/groups/#{group['id']}/unpin"
+
+ req = Net::HTTP::Post.new(url)
+ req['Content-Type'] = 'application/json'
+ req.body = data
+
+ res = net.request(req)
+
+ case res.code
+ when '204'
+ puts "Successfully unpinned nodes '#{nodes.join(', ')}' from group '#{group['name']}'"
+ else
+ begin
+ error_body = JSON.parse(res.body.to_s)
+ raise "Failed to unpin nodes: #{error_body['kind'] || error_body}"
+ rescue JSON::ParserError
+ raise "Invalid response from server (status #{res.code}): #{res.body}"
+ end
+ end
+ rescue StandardError => e
+ raise "Error during unpin request: #{e.message}"
+ end
+ end
+
+ # Utility class to aid in retrieving useful information from the node group
+ # data
+ class NodeGroup
+ attr_reader :data
+
+ def initialize(data)
+ @data = data
+ end
+
+ # Aids in digging into node groups by name, rather than UUID
+ def dig(name, *args)
+ group = @data.find { |obj| obj['name'] == name }
+ if group.nil?
+ nil
+ elsif args.empty?
+ group
+ else
+ group.dig(*args)
+ end
+ end
+ end
+
+ def execute!
+ group_name = @params['group_name']
+ node_certnames = @params['node_certnames']
+ group = groups.dig(group_name)
+ if group
+ unpin_node(group, node_certnames)
+ puts "Unpinned #{node_certnames.join(', ')} from #{group_name}"
+ else
+ puts "Group #{group_name} not found"
+ end
+ end
+end
+
+# Run the task unless an environment flag has been set
+unless ENV['RSPEC_UNIT_TEST_MODE']
+ Puppet.initialize_settings
+ task = NodeGroupUnpin.new(JSON.parse(STDIN.read))
+ task.execute!
+end
diff --git a/tasks/update_pe_master_rules.json b/tasks/update_pe_master_rules.json
new file mode 100644
index 00000000..664e89c3
--- /dev/null
+++ b/tasks/update_pe_master_rules.json
@@ -0,0 +1,8 @@
+{
+ "description": "Updates the PE Master group rules to replace pe_compiler with a regex match for any pe_compiler role",
+ "input_method": "stdin",
+ "private": true,
+ "implementations": [
+ {"name": "update_pe_master_rules.rb"}
+ ]
+}
\ No newline at end of file
diff --git a/tasks/update_pe_master_rules.rb b/tasks/update_pe_master_rules.rb
new file mode 100755
index 00000000..85219daf
--- /dev/null
+++ b/tasks/update_pe_master_rules.rb
@@ -0,0 +1,117 @@
+#!/opt/puppetlabs/puppet/bin/ruby
+# frozen_string_literal: true
+
+require 'json'
+require 'net/https'
+require 'puppet'
+
+# UpdatePeMasterRules task class
+class UpdatePeMasterRules
+ def initialize(params)
+ @params = params
+ end
+
+ def https_client
+ client = Net::HTTP.new(Puppet.settings[:certname], 4433)
+ client.use_ssl = true
+ client.cert = @cert ||= OpenSSL::X509::Certificate.new(File.read(Puppet.settings[:hostcert]))
+ client.key = @key ||= OpenSSL::PKey::RSA.new(File.read(Puppet.settings[:hostprivkey]))
+ client.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ client.ca_file = Puppet.settings[:localcacert]
+ client
+ end
+
+ def get_pe_master_group_id
+ net = https_client
+ res = net.get('/classifier-api/v1/groups')
+
+ unless res.code == '200'
+ raise "Failed to fetch groups: HTTP #{res.code} - #{res.body}"
+ end
+
+ groups = JSON.parse(res.body)
+ pe_master_group = groups.find { |group| group['name'] == 'PE Master' }
+
+ raise "Could not find PE Master group" unless pe_master_group
+ pe_master_group['id']
+ rescue JSON::ParserError => e
+ raise "Invalid JSON response from server: #{e.message}"
+ rescue StandardError => e
+ raise "Error fetching PE Master group ID: #{e.message}"
+ end
+
+ def get_current_rules(group_id)
+ net = https_client
+ url = "/classifier-api/v1/groups/#{group_id}/rules"
+ req = Net::HTTP::Get.new(url)
+ res = net.request(req)
+
+ unless res.code == '200'
+ raise "Failed to fetch rules: HTTP #{res.code} - #{res.body}"
+ end
+
+ JSON.parse(res.body)['rule']
+ rescue JSON::ParserError => e
+ raise "Invalid JSON response from server: #{e.message}"
+ rescue StandardError => e
+ raise "Error fetching rules: #{e.message}"
+ end
+
+ def transform_rule(rule)
+ return rule unless rule.is_a?(Array)
+
+ if rule[0] == '=' &&
+ rule[1].is_a?(Array) &&
+ rule[1] == ['trusted', 'extensions', 'pp_auth_role'] &&
+ rule[2] == 'pe_compiler'
+ return ['~', ['trusted', 'extensions', 'pp_auth_role'], '^pe_compiler(?:_legacy)?$']
+ end
+
+ # Recursively transform nested rules
+ rule.map { |element| transform_rule(element) }
+ end
+
+ def update_rules(group_id)
+ net = https_client
+ begin
+ current_rules = get_current_rules(group_id)
+
+ # Transform rules recursively to handle nested structures
+ new_rules = transform_rule(current_rules)
+
+ # Update the group with the modified rules
+ url = "/classifier-api/v1/groups/#{group_id}"
+ req = Net::HTTP::Post.new(url)
+ req['Content-Type'] = 'application/json'
+ req.body = { rule: new_rules }.to_json
+
+ res = net.request(req)
+
+ case res.code
+ when '200', '201', '204'
+ puts "Successfully transformed pe_compiler rule to use regex match for *_compiler roles in group #{group_id}"
+ else
+ begin
+ error_body = JSON.parse(res.body.to_s)
+ raise "Failed to update rules: #{error_body['kind'] || error_body}"
+ rescue JSON::ParserError
+ raise "Invalid response from server (status #{res.code}): #{res.body}"
+ end
+ end
+ rescue StandardError => e
+ raise "Error during rules update: #{e.message}"
+ end
+ end
+
+ def execute!
+ group_id = get_pe_master_group_id
+ update_rules(group_id)
+ end
+end
+
+# Run the task unless an environment flag has been set
+unless ENV['RSPEC_UNIT_TEST_MODE']
+ Puppet.initialize_settings
+ task = UpdatePeMasterRules.new(JSON.parse(STDIN.read))
+ task.execute!
+end
\ No newline at end of file