|  | 
|  | 1 | +#!/opt/puppetlabs/puppet/bin/ruby | 
|  | 2 | +# frozen_string_literal: true | 
|  | 3 | + | 
|  | 4 | +require 'json' | 
|  | 5 | +require 'yaml' | 
|  | 6 | +require 'net/https' | 
|  | 7 | +require 'puppet' | 
|  | 8 | + | 
|  | 9 | +# NodeGroupUnpin task class | 
|  | 10 | +class NodeGroupUnpin | 
|  | 11 | +  def initialize(params) | 
|  | 12 | +    @params = params | 
|  | 13 | +    raise "Missing required parameter 'node_certname'" unless @params['node_certname'] | 
|  | 14 | +    raise "Missing required parameter 'group_name'" unless @params['group_name'] | 
|  | 15 | +    @auth = YAML.load_file('/etc/puppetlabs/puppet/classifier.yaml') | 
|  | 16 | +  rescue Errno::ENOENT | 
|  | 17 | +    raise 'Could not find classifier.yaml at /etc/puppetlabs/puppet/classifier.yaml' | 
|  | 18 | +  end | 
|  | 19 | + | 
|  | 20 | +  def https_client | 
|  | 21 | +    client = Net::HTTP.new(Puppet.settings[:certname], 4433) | 
|  | 22 | +    client.use_ssl = true | 
|  | 23 | +    client.cert = @cert ||= OpenSSL::X509::Certificate.new(File.read(Puppet.settings[:hostcert])) | 
|  | 24 | +    client.key = @key ||= OpenSSL::PKey::RSA.new(File.read(Puppet.settings[:hostprivkey])) | 
|  | 25 | +    client.verify_mode = OpenSSL::SSL::VERIFY_PEER | 
|  | 26 | +    client.ca_file = Puppet.settings[:localcacert] | 
|  | 27 | +    client | 
|  | 28 | +  end | 
|  | 29 | + | 
|  | 30 | +  def groups | 
|  | 31 | +    @groups ||= begin | 
|  | 32 | +      net = https_client | 
|  | 33 | +      res = net.get('/classifier-api/v1/groups') | 
|  | 34 | + | 
|  | 35 | +      unless res.code == '200' | 
|  | 36 | +        raise "Failed to fetch groups: HTTP #{res.code} - #{res.body}" | 
|  | 37 | +      end | 
|  | 38 | + | 
|  | 39 | +      NodeGroup.new(JSON.parse(res.body)) | 
|  | 40 | +                rescue JSON::ParserError => e | 
|  | 41 | +                  raise "Invalid JSON response from server: #{e.message}" | 
|  | 42 | +                rescue StandardError => e | 
|  | 43 | +                  raise "Error fetching groups: #{e.message}" | 
|  | 44 | +    end | 
|  | 45 | +  end | 
|  | 46 | + | 
|  | 47 | +  def unpin_node(group, node) | 
|  | 48 | +    raise 'Invalid group object' unless group.is_a?(Hash) && group['id'] && group['name'] | 
|  | 49 | + | 
|  | 50 | +    net = https_client | 
|  | 51 | +    begin | 
|  | 52 | +      data = { "nodes": [node] }.to_json | 
|  | 53 | +      url = "/classifier-api/v1/groups/#{group['id']}/unpin" | 
|  | 54 | + | 
|  | 55 | +      req = Net::HTTP::Post.new(url) | 
|  | 56 | +      req['Content-Type'] = 'application/json' | 
|  | 57 | +      req.body = data | 
|  | 58 | + | 
|  | 59 | +      res = net.request(req) | 
|  | 60 | + | 
|  | 61 | +      case res.code | 
|  | 62 | +      when '204' | 
|  | 63 | +        puts "Successfully unpinned node '#{node}' from group '#{group['name']}'" | 
|  | 64 | +      else | 
|  | 65 | +        begin | 
|  | 66 | +          error_body = JSON.parse(res.body.to_s) | 
|  | 67 | +          raise "Failed to unpin node: #{error_body['kind'] || error_body}" | 
|  | 68 | +        rescue JSON::ParserError | 
|  | 69 | +          raise "Invalid response from server (status #{res.code}): #{res.body}" | 
|  | 70 | +        end | 
|  | 71 | +      end | 
|  | 72 | +    rescue StandardError => e | 
|  | 73 | +      raise "Error during unpin request: #{e.message}" | 
|  | 74 | +    end | 
|  | 75 | +  end | 
|  | 76 | + | 
|  | 77 | +  # Utility class to aid in retrieving useful information from the node group | 
|  | 78 | +  # data | 
|  | 79 | +  class NodeGroup | 
|  | 80 | +    attr_reader :data | 
|  | 81 | + | 
|  | 82 | +    def initialize(data) | 
|  | 83 | +      @data = data | 
|  | 84 | +    end | 
|  | 85 | + | 
|  | 86 | +    # Aids in digging into node groups by name, rather than UUID | 
|  | 87 | +    def dig(name, *args) | 
|  | 88 | +      group = @data.find { |obj| obj['name'] == name } | 
|  | 89 | +      if group.nil? | 
|  | 90 | +        nil | 
|  | 91 | +      elsif args.empty? | 
|  | 92 | +        group | 
|  | 93 | +      else | 
|  | 94 | +        group.dig(*args) | 
|  | 95 | +      end | 
|  | 96 | +    end | 
|  | 97 | +  end | 
|  | 98 | + | 
|  | 99 | +  def execute! | 
|  | 100 | +    group_name = @params['group_name'] | 
|  | 101 | +    node_certname = @params['node_certname'] | 
|  | 102 | +    group = groups.dig(group_name) | 
|  | 103 | +    if group | 
|  | 104 | +      unpin_node(group, node_certname) | 
|  | 105 | +      puts "Unpinned #{node_certname} from #{group_name}" | 
|  | 106 | +    else | 
|  | 107 | +      puts "Group #{group_name} not found" | 
|  | 108 | +    end | 
|  | 109 | +  end | 
|  | 110 | +end | 
|  | 111 | + | 
|  | 112 | +# Run the task unless an environment flag has been set | 
|  | 113 | +unless ENV['RSPEC_UNIT_TEST_MODE'] | 
|  | 114 | +  Puppet.initialize_settings | 
|  | 115 | +  task = NodeGroupUnpin.new(JSON.parse(STDIN.read)) | 
|  | 116 | +  task.execute! | 
|  | 117 | +end | 
0 commit comments