From b5b9ca5e3c71297fb8d21b9ba505214a2a5b731c Mon Sep 17 00:00:00 2001 From: petergmurphy Date: Wed, 15 Jan 2025 15:12:11 +0000 Subject: [PATCH] (PE-39577) Optimise legacy compiler support This commit: - Adds the `node_group_unpin` task. - `node_group_unpin` task is called in the convert plan to remove legacy compilers from from the PE Master node group. - Legacy compilers `pp_auth_role` changed to `legacy_compiler`. - Changes the PEADM config to use the PE Certificate Authority node group. --- plans/convert.pp | 14 ++++- spec/plans/convert_spec.rb | 3 +- tasks/get_peadm_config.rb | 2 +- tasks/node_group_unpin.json | 17 ++++++ tasks/node_group_unpin.rb | 117 ++++++++++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 tasks/node_group_unpin.json create mode 100644 tasks/node_group_unpin.rb diff --git a/plans/convert.pp b/plans/convert.pp index 1ff1771a..234b9110 100644 --- a/plans/convert.pp +++ b/plans/convert.pp @@ -232,7 +232,7 @@ 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') => 'legacy_compiler', peadm::oid('peadm_availability_group') => 'A', peadm::oid('peadm_legacy_compiler') => 'true', }, @@ -242,7 +242,7 @@ 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') => 'legacy_compiler', peadm::oid('peadm_availability_group') => 'B', peadm::oid('peadm_legacy_compiler') => 'true', }, @@ -283,6 +283,16 @@ include peadm::setup::convert_node_manager } + + # Unpin legacy compilers from PE Master group + if $legacy_compiler_targets { + $legacy_compiler_targets.each |$target| { + run_task('peadm::node_group_unpin', $primary_target, + selected_node => $target.peadm::certname(), + group => 'PE Master', + ) + } + } } else { # lint:ignore:strict_indent diff --git a/spec/plans/convert_spec.rb b/spec/plans/convert_spec.rb index 39ec7367..533eff98 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_compiler_host' => 'legacy_compiler' } end it 'single primary no dr valid' do @@ -21,6 +21,7 @@ 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_certname' => 'legacy_compiler', 'group_name' => 'PE Master' }) # 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..04118b8f 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) diff --git a/tasks/node_group_unpin.json b/tasks/node_group_unpin.json new file mode 100644 index 00000000..806a1abf --- /dev/null +++ b/tasks/node_group_unpin.json @@ -0,0 +1,17 @@ +{ + "description": "Unpins a node from a specified PE node group", + "parameters": { + "node_certname": { + "type": "String", + "description": "The certname of the node to unpin" + }, + "group_name": { + "type": "String", + "description": "The name of the node group to unpin the node 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 100644 index 00000000..c1594f62 --- /dev/null +++ b/tasks/node_group_unpin.rb @@ -0,0 +1,117 @@ +#!/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_certname'" unless @params['node_certname'] + 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, node) + raise 'Invalid group object' unless group.is_a?(Hash) && group['id'] && group['name'] + + net = https_client + begin + data = { "nodes": [node] }.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 node '#{node}' from group '#{group['name']}'" + else + begin + error_body = JSON.parse(res.body.to_s) + raise "Failed to unpin node: #{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_certname = @params['node_certname'] + group = groups.dig(group_name) + if group + unpin_node(group, node_certname) + puts "Unpinned #{node_certname} 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