├── .gitignore ├── CODEOWNERS ├── Gemfile ├── README.md ├── bin └── puppet-classify ├── lib ├── puppet_https.rb ├── puppetclassify.rb └── puppetclassify │ ├── classes.rb │ ├── classification.rb │ ├── commands.rb │ ├── environments.rb │ ├── groups.rb │ ├── import_hierarchy.rb │ ├── last_class_update.rb │ ├── nodes.rb │ ├── rules.rb │ ├── update_classes.rb │ └── validate.rb ├── puppetclassify.gemspec └── spec ├── puppet_https_spec.rb ├── puppetclassify ├── classes_spec.rb ├── commands_spec.rb ├── environments_spec.rb ├── groups_spec.rb ├── import_hierarchy_spec.rb ├── last_class_update_spec.rb ├── nodes_spec.rb ├── rules_spec.rb ├── update_classes_spec.rb └── validate_spec.rb ├── puppetclassify_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /lib/bundler/man/ 26 | 27 | # for a library or gem, you might want to ignore these files since the code is 28 | # intended to run in multiple environments; otherwise, check them in: 29 | Gemfile.lock 30 | .ruby-version 31 | .ruby-gemset 32 | 33 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 34 | .rvmrc 35 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # default to the dumpling team 2 | * @puppetlabs/dumpling 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rspec' 4 | gem 'webmock' 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # puppet classify 2 | 3 | A ruby library to interface with the classifier service 4 | 5 | [![Gem Version](https://badge.fury.io/rb/puppetclassify.svg)](https://badge.fury.io/rb/puppetclassify) 6 | 7 | ## How to install 8 | 9 | ``` 10 | gem install puppetclassify 11 | ``` 12 | 13 | ### Locally 14 | 15 | ``` 16 | gem build puppetclassify.gemspec 17 | gem install puppetclassify-0.1.0.gem 18 | ``` 19 | 20 | ## Maintenance 21 | 22 | Maintainers: [Puppet](https://github.com/puppetlabs) 23 | 24 | Tickets: Open an issue or pull request directly on this repository 25 | 26 | ## How to use 27 | 28 | Here is the basic configuration you'll need to use the puppetclassify class with certificate auth: 29 | 30 | ```ruby 31 | require 'puppetclassify' 32 | # URL of classifier as well as certificates and private key for auth 33 | auth_info = { 34 | "ca_certificate_path" => "/etc/puppetlabs/puppet/ssl/certs/ca.pem", 35 | "certificate_path" => "/etc/puppetlabs/puppet/ssl/certs/myhostname.vm.pem", 36 | "private_key_path" => "/etc/puppetlabs/puppet/ssl/private_keys/myhostname.vm.pem", 37 | "read_timeout" => 90 # optional timeout, defaults to 90 if this key doesn't exist 38 | } 39 | 40 | classifier_url = 'https://puppetmaster.local:4433/classifier-api' 41 | puppetclassify = PuppetClassify.new(classifier_url, auth_info) 42 | ``` 43 | 44 | You can also use token auth by either supplying a path to a token file: 45 | 46 | ```ruby 47 | auth_info = { 48 | "ca_certificate_path" => "/etc/puppetlabs/puppet/ssl/certs/ca.pem", 49 | "token_path" => "/home/sam/.puppetlabs/token", 50 | } 51 | ``` 52 | 53 | Or by specifying a token string directly: 54 | 55 | ```ruby 56 | token = 'eyJhbGciOiJSUzUxM....' 57 | auth_info = { 58 | "ca_certificate_path" => "/etc/puppetlabs/puppet/ssl/certs/ca.pem", 59 | "token" => token, 60 | } 61 | ``` 62 | 63 | If you have a token file at `~/.puppetlabs/token` then you can make use of it by not specifying any authentication info, ie: 64 | 65 | ```ruby 66 | auth_info = { 67 | "ca_certificate_path" => "/etc/puppetlabs/puppet/ssl/certs/ca.pem", 68 | } 69 | ``` 70 | 71 | ### Basic case 72 | 73 | If you are wanting to get all of the groups the classifier knows about: 74 | 75 | ```ruby 76 | # Get all the groups 77 | puppetclassify.groups.get_groups 78 | ``` 79 | 80 | ### Taking action on a specific group by name 81 | 82 | If you have a group you want to modify, but do not know the group ID: 83 | 84 | ```ruby 85 | my_group_id = puppetclassify.groups.get_group_id("My Group Name") 86 | group_delta = {"variables"=>{"key"=>"value"}, "id"=>my_group_id, "classes"=>{"motd"=>{"content"=>"hello!"}}} # an example to update a groups variables and classes 87 | puppetclassify.groups.update_group(group_delta) 88 | ``` 89 | 90 | ### Retrieving classification of a node 91 | 92 | Because the Console classifies nodes based on rules, you may want to submit a 93 | complete `facts` hash for rules to match. See [the API docs](https://docs.puppetlabs.com/pe/latest/nc_classification.html) 94 | for examples. 95 | 96 | You can retrieve facts for the local node either using Facter from Ruby or the 97 | command line. For example: 98 | 99 | ```ruby 100 | #! /opt/puppetlabs/puppet/bin/ruby 101 | require 'facter' 102 | 103 | facts = { 'fact' => Facter.to_hash } 104 | ``` 105 | 106 | or: 107 | 108 | ```ruby 109 | #! /usr/bin/env ruby 110 | require 'json' 111 | 112 | facts = { 'fact' => JSON.parse(`facter -j`) } 113 | ``` 114 | 115 | Once you have the facts, retrieving classification of a node is simple: 116 | 117 | ```ruby 118 | #! /opt/puppetlabs/puppet/bin/ruby 119 | require 'facter' 120 | require 'puppetclassify' 121 | 122 | # NOTE: Add setup information here for puppetclassify 123 | 124 | # gather facts 125 | facts = { 'fact' => Facter.to_hash } 126 | 127 | # Get a node's classification 128 | puppetclassify.classification.get('myhostname.puppetlabs.vm', facts) 129 | ``` 130 | 131 | ### Pinning and unpinning nodes to a group in Puppet enterprise 132 | 133 | If you want to "pin" a node to a specific group so it gets that classification, you can 134 | invoke the pin_nodes command. And if you want to remove nodes from that group, you can run 135 | the unpin_nodes command. 136 | 137 | If you want to remove nodes from every group they are pinned to, use the unpin_from_all command. 138 | 139 | ```ruby 140 | my_group_id = puppetclassify.groups.get_group_id("My Super Awesome Group Name") 141 | 142 | nodes = ["hostname.com", "myotherhost.com", "anotherhost.com"] 143 | # pin nodes to group 144 | puppetclassify.groups.pin_nodes(my_group_id, nodes) 145 | 146 | # unpin nodes from group 147 | puppetclassify.groups.unpin_nodes(my_group_id, nodes) 148 | 149 | # unpin nodes from EVERY group 150 | puppetclassify.commands.unpin_from_all(nodes) 151 | ``` 152 | 153 | ## Library Docs 154 | 155 | [rubydoc](http://www.rubydoc.info/gems/puppetclassify/0.1.0) 156 | -------------------------------------------------------------------------------- /bin/puppet-classify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) 4 | -------------------------------------------------------------------------------- /lib/puppet_https.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | require 'net/https' 3 | 4 | class PuppetHttps 5 | attr_reader :auth_method, :token_path 6 | 7 | def initialize(settings) 8 | # Settings hash: 9 | # - ca_certificate_path 10 | # - certificate_path (optional) 11 | # - private_key_path (optional) 12 | # - read_timeout (optional) 13 | # - token_path (default: $HOME/.puppetlabs/token) 14 | # - token (optional, takes precedence over token_path) 15 | # 16 | # token auth takes precedence over cert auth (in the case that both methods are provided) 17 | 18 | default_token_path = ENV['HOME'].nil? ? nil : File.join(ENV['HOME'], '.puppetlabs', 'token') 19 | 20 | ca_cert_path = settings['ca_certificate_path'] 21 | cert_path = settings['certificate_path'] 22 | pkey_path = settings['private_key_path'] 23 | 24 | @ca_file = settings['ca_certificate_path'] if ca_cert_path and File.exist?(ca_cert_path) 25 | @read_timeout = settings['read_timeout'] || 90 # A default timeout value in seconds 26 | 27 | @auth_method = case 28 | when (settings['token'] or settings['token_path']) 29 | 'token' 30 | when (cert_path and pkey_path) 31 | 'cert' 32 | when default_token_path && File.exist?(default_token_path) 33 | 'token' 34 | else 35 | nil 36 | end 37 | 38 | unless @auth_method 39 | raise RuntimeError, "No authentication methods available." 40 | end 41 | 42 | case @auth_method 43 | when 'token' 44 | @token = settings['token'] 45 | @token_path = (settings['token_path'] || default_token_path) unless @token 46 | # Make sure we have a token and it's not empty 47 | case 48 | when (@token and @token.empty?) 49 | raise RuntimeError, "Received an empty string for token" 50 | when (not @token and not File.exist?(@token_path)) 51 | raise RuntimeError, "Token file not found at [#{@token_path}]" 52 | when (not @token and File.zero?(@token_path)) 53 | raise RuntimeError, "Token file at [#{@token_path}] is empty" 54 | end 55 | when 'cert' 56 | if File.exist?(cert_path) and File.exist?(pkey_path) 57 | @cert = OpenSSL::X509::Certificate.new(File.read(cert_path)) 58 | @key = OpenSSL::PKey::RSA.new(File.read(pkey_path)) 59 | else 60 | raise RuntimeError, "Certificate auth requested but certificate or private key cannot be found." 61 | end 62 | end 63 | 64 | 65 | end 66 | 67 | def make_ssl_request(url, req) 68 | connection = Net::HTTP.new(url.host, url.port) 69 | 70 | # connection.set_debug_output $stderr 71 | 72 | connection.use_ssl = true 73 | connection.ssl_version = :TLSv1_2 74 | connection.verify_mode = OpenSSL::SSL::VERIFY_PEER 75 | connection.ca_file = @ca_file if @ca_file 76 | connection.read_timeout = @read_timeout 77 | 78 | if @auth_method == 'cert' 79 | connection.cert = @cert 80 | connection.key = @key 81 | end 82 | 83 | connection.start { |http| http.request(req) } 84 | end 85 | 86 | def put(url, request_body=nil) 87 | url = URI.parse(url) 88 | req = Net::HTTP::Put.new(url.path, self.auth_header) 89 | req.content_type = 'application/json' 90 | 91 | unless request_body.nil? 92 | req.body = request_body 93 | end 94 | 95 | res = make_ssl_request(url, req) 96 | end 97 | 98 | def get(url) 99 | url = URI.parse(url) 100 | accept = 'application/json' 101 | req = Net::HTTP::Get.new("#{url.path}?#{url.query}", {"Accept" => accept}.merge(self.auth_header)) 102 | res = make_ssl_request(url, req) 103 | res 104 | end 105 | 106 | def post(url, request_body=nil) 107 | url = URI.parse(url) 108 | 109 | request = Net::HTTP::Post.new(url.request_uri, self.auth_header) 110 | request.content_type = 'application/json' 111 | 112 | unless request_body.nil? 113 | request.body = request_body 114 | end 115 | 116 | res = make_ssl_request(url, request) 117 | res 118 | end 119 | 120 | def delete(url) 121 | url = URI.parse(url) 122 | 123 | request = Net::HTTP::Delete.new(url.request_uri, self.auth_header) 124 | request.content_type = 'application/json' 125 | 126 | res = make_ssl_request(url, request) 127 | res 128 | end 129 | 130 | #private 131 | 132 | def token 133 | return @token if @token 134 | if @token_path and File.exist?(@token_path) 135 | @token = File.read(@token_path) 136 | return @token 137 | end 138 | return nil 139 | end 140 | 141 | def auth_header 142 | token = self.token 143 | header = token ? {"X-Authentication" => token} : {} 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /lib/puppetclassify.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | require 'puppetclassify/groups' 3 | require 'puppetclassify/environments' 4 | require 'puppetclassify/classes' 5 | require 'puppetclassify/nodes' 6 | require 'puppetclassify/import_hierarchy' 7 | require 'puppetclassify/update_classes' 8 | require 'puppetclassify/validate' 9 | require 'puppetclassify/rules' 10 | require 'puppetclassify/last_class_update' 11 | require 'puppetclassify/classification' 12 | require 'puppetclassify/commands' 13 | 14 | class PuppetClassify 15 | def initialize(nc_api_url, https_settings) 16 | @nc_api_url = nc_api_url 17 | @puppet_https = PuppetHttps.new(https_settings) 18 | end 19 | 20 | def groups 21 | if @groups 22 | @groups 23 | else 24 | @groups = Groups.new(@nc_api_url, @puppet_https) 25 | end 26 | end 27 | 28 | def nodes 29 | if @nodes 30 | @nodes 31 | else 32 | @nodes = Nodes.new(@nc_api_url, @puppet_https) 33 | end 34 | end 35 | 36 | def environments 37 | if @environments 38 | @environments 39 | else 40 | @environments = Environments.new(@nc_api_url, @puppet_https) 41 | end 42 | end 43 | 44 | def classes 45 | if @classes 46 | @classes 47 | else 48 | @classes = Classes.new(@nc_api_url, @puppet_https) 49 | end 50 | end 51 | 52 | def import_hierarchy 53 | if @import_hierarchy 54 | @import_hierarchy 55 | else 56 | @import_hierarchy = ImportHierarchy.new(@nc_api_url, @puppet_https) 57 | end 58 | end 59 | 60 | def update_classes 61 | if @update_classes 62 | @update_classes 63 | else 64 | @update_classes = UpdateClasses.new(@nc_api_url, @puppet_https) 65 | end 66 | end 67 | 68 | def validate 69 | if @validate 70 | @validate 71 | else 72 | @validate = Validate.new(@nc_api_url, @puppet_https) 73 | end 74 | end 75 | 76 | def rules 77 | if @rules 78 | @rules 79 | else 80 | @rules = Rules.new(@nc_api_url, @puppet_https) 81 | end 82 | end 83 | 84 | def last_class_update 85 | if @last_class_update 86 | @last_class_update 87 | else 88 | @last_class_update = LastClassUpdate.new(@nc_api_url, @puppet_https) 89 | end 90 | end 91 | 92 | def classification 93 | if @classification 94 | @classification 95 | else 96 | @classification = Classification.new(@nc_api_url, @puppet_https) 97 | end 98 | end 99 | 100 | def commands 101 | if @commands 102 | @commands 103 | else 104 | @commands = Commands.new(@nc_api_url, @puppet_https) 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/puppetclassify/classes.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | 3 | class Classes 4 | def initialize(nc_api_url, puppet_https) 5 | @nc_api_url = nc_api_url 6 | @puppet_https = puppet_https 7 | end 8 | 9 | def get_classes 10 | class_res = @puppet_https.get("#{@nc_api_url}/v1/classes") 11 | JSON.parse(class_res.body) 12 | end 13 | 14 | def get_environment_classes(environment) 15 | class_res = @puppet_https.get("#{@nc_api_url}/v1/environments/#{environment}/classes") 16 | JSON.parse(class_res.body) 17 | end 18 | 19 | def get_environment_class(environment, class_name) 20 | class_res = @puppet_https.get("#{@nc_api_url}/v1/environments/#{environment}/classes/#{class_name}") 21 | JSON.parse(class_res.body) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/puppetclassify/classification.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | 3 | class Classification 4 | def initialize(nc_api_url, puppet_https) 5 | @nc_api_url = nc_api_url 6 | @puppet_https = puppet_https 7 | end 8 | 9 | def get(name, facts={}) 10 | unless facts.class == Hash 11 | STDERR.puts "Facts should be a hash, not a #{facts.class}" 12 | return false 13 | end 14 | 15 | class_res = @puppet_https.post("#{@nc_api_url}/v1/classified/nodes/#{name}", facts.to_json) 16 | 17 | unless class_res.code.to_i == 200 18 | STDERR.puts "An error occured retreiving the classification of node #{name}: HTTP #{class_res.code} #{class_res.message}" 19 | STDERR.puts class_res.body 20 | else 21 | JSON.parse(class_res.body) 22 | end 23 | end 24 | 25 | def explain(name, facts={}) 26 | unless facts.class == Hash 27 | STDERR.puts "Facts should be a hash, not a #{facts.class}" 28 | return false 29 | end 30 | 31 | class_res = @puppet_https.post("#{@nc_api_url}/v1/classified/nodes/#{name}/explanation", facts.to_json) 32 | 33 | unless class_res.code.to_i == 200 34 | STDERR.puts "An error occured retreiving the classification explanation of node #{name}: HTTP #{class_res.code} #{class_res.message}" 35 | STDERR.puts class_res.body 36 | else 37 | JSON.parse(class_res.body) 38 | end 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /lib/puppetclassify/commands.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | require 'json' 3 | 4 | class Commands 5 | def initialize(nc_api_url, puppet_https) 6 | @puppet_https = puppet_https 7 | @nc_api_url = nc_api_url 8 | end 9 | 10 | def unpin_from_all(nodes) 11 | all_nodes = {} 12 | all_nodes['nodes'] = nodes 13 | 14 | response = @puppet_https.post("#{@nc_api_url}/v1/commands/unpin-from-all", all_nodes.to_json) 15 | 16 | unless response.code.to_i != 200 17 | nodez = JSON.parse(response.body) 18 | else 19 | STDERR.puts "An error occured with your request: HTTP #{response.code} #{response.message}" 20 | STDERR.puts response.body 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/puppetclassify/environments.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | 3 | class Environments 4 | def initialize(nc_api_url, puppet_https) 5 | @nc_api_url = nc_api_url 6 | @puppet_https = puppet_https 7 | end 8 | 9 | def get_environments 10 | env_res = @puppet_https.get("#{@nc_api_url}/v1/environments") 11 | JSON.parse(env_res.body) 12 | end 13 | 14 | def get_environment(name) 15 | env_res = @puppet_https.get("#{@nc_api_url}/v1/environments/#{name}") 16 | JSON.parse(env_res.body) 17 | end 18 | 19 | def create_environment(name) 20 | env_res = @puppet_https.put("#{@nc_api_url}/v1/environments/#{name}") 21 | 22 | unless env_res.code.to_i == 201 23 | STDERR.puts "An error occured saving the environment: HTTP #{env_res.code} #{env_res.message}" 24 | STDERR.puts env_res.body 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/puppetclassify/groups.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | require 'json' 3 | 4 | class Groups 5 | def initialize(nc_api_url, puppet_https) 6 | @nc_api_url = nc_api_url 7 | @puppet_https = puppet_https 8 | end 9 | 10 | def get_group(group_id) 11 | # HTTP GET 12 | group_res = @puppet_https.get("#{@nc_api_url}/v1/groups/#{group_id}") 13 | unless group_res.code.to_i != 200 14 | JSON.parse(group_res.body) 15 | else 16 | STDERR.puts "An error occured with your request: HTTP #{group_res.code} #{group_res.message}" 17 | STDERR.puts group_res.body 18 | end 19 | end 20 | 21 | def get_group_id(group_name) 22 | groups_res = @puppet_https.get("#{@nc_api_url}/v1/groups") 23 | 24 | unless groups_res.code.to_i != 200 25 | groups = JSON.parse(groups_res.body) 26 | else 27 | STDERR.puts "An error occured with your request: HTTP #{groups_res.code} #{groups_res.message}" 28 | STDERR.puts groups_res.body 29 | end 30 | 31 | group_info = groups.find { |group| group['name'] == group_name } 32 | 33 | if group_info.nil? 34 | STDERR.puts "Could not find group with the name #{group_name}" 35 | else 36 | group_info['id'] 37 | end 38 | end 39 | 40 | def get_groups 41 | group_res = @puppet_https.get("#{@nc_api_url}/v1/groups") 42 | JSON.parse(group_res.body) 43 | end 44 | 45 | def create_group(group_info) 46 | if group_info['id'] 47 | # HTTP PUT /v1/groups/:id 48 | res = @puppet_https.put("#{@nc_api_url}/v1/groups/#{group_info['id']}", group_info.to_json) 49 | else 50 | # HTTP POST /v1/groups 51 | res = @puppet_https.post("#{@nc_api_url}/v1/groups", group_info.to_json) 52 | end 53 | 54 | if res.code.to_i >= 400 55 | STDERR.puts "An error occured creating the group: HTTP #{res.code} #{res.message}" 56 | STDERR.puts res.body 57 | else 58 | unless group_info['id'] 59 | res['location'].split("/")[-1] 60 | else 61 | group_info['id'] 62 | end 63 | end 64 | end 65 | 66 | def update_group(group_info_delta) 67 | # HTTP POST /v1/groups/:id 68 | group_res = @puppet_https.post("#{@nc_api_url}/v1/groups/#{group_info_delta['id']}", group_info_delta.to_json) 69 | 70 | unless group_res.code.to_i == 200 71 | STDERR.puts "Update Group failed: HTTP #{group_res.code} #{group_res.message}" 72 | STDERR.puts group_res.body 73 | end 74 | end 75 | 76 | def delete_group(group_id) 77 | group_res = @puppet_https.delete("#{@nc_api_url}/v1/groups/#{group_id}") 78 | if group_res.code.to_i != 204 79 | STDERR.puts "An error occured deleting the group: HTTP #{group_res.code} #{group_res.message}" 80 | STDERR.puts group_res.body 81 | end 82 | end 83 | 84 | def pin_nodes(group_id, node_hash) 85 | request_body = {} 86 | request_body["nodes"] = node_hash # expects node_hash to be array, i.e. ["foo", "bar", "baz"] 87 | group_response = @puppet_https.post("#{@nc_api_url}/v1/groups/#{group_id}/pin", request_body.to_json) 88 | 89 | unless group_response.code.to_i == 204 90 | STDERR.puts "An error occured pinning nodes the group: HTTP #{group_response.code} #{group_response.message}" 91 | STDERR.puts group_response.body 92 | end 93 | end 94 | 95 | def unpin_nodes(group_id, node_hash) 96 | request_body = {} 97 | request_body["nodes"] = node_hash # expects node_hash to be array, i.e. ["foo", "bar", "baz"] 98 | group_response = @puppet_https.post("#{@nc_api_url}/v1/groups/#{group_id}/unpin", request_body.to_json) 99 | 100 | unless group_response.code.to_i == 204 101 | STDERR.puts "An error occured unpinning nodes the group: HTTP #{group_response.code} #{group_response.message}" 102 | STDERR.puts group_response.body 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/puppetclassify/import_hierarchy.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | 3 | class ImportHierarchy 4 | def initialize(nc_api_url, puppet_https) 5 | @nc_api_url = nc_api_url 6 | @puppet_https = puppet_https 7 | end 8 | 9 | def import(group_hierarchy) 10 | import_res = @puppet_https.post("#{@nc_api_url}/v1/import-hierarchy", group_hierarchy.to_json) 11 | 12 | if import_res.code.to_i >= 400 13 | STDERR.puts "An error has occured during import: HTTP #{import_res.code} #{import_res.message}" 14 | STDERR.puts import_res.body 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/puppetclassify/last_class_update.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | 3 | class LastClassUpdate 4 | def initialize(nc_api_url, puppet_https) 5 | @nc_api_url = nc_api_url 6 | @puppet_https = puppet_https 7 | end 8 | 9 | def get 10 | class_update_res = @puppet_https.get("#{@nc_api_url}/v1/last-class-update") 11 | 12 | JSON.parse(class_update_res.body) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/puppetclassify/nodes.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | 3 | class Nodes 4 | def initialize(nc_api_url, puppet_https) 5 | @puppet_https = puppet_https 6 | @nc_api_url = nc_api_url 7 | end 8 | 9 | def get_nodes 10 | node_res = @puppet_https.get("#{@nc_api_url}/v1/nodes") 11 | JSON.parse(node_res.body) 12 | end 13 | 14 | def get_node(node_name) 15 | node_res = @puppet_https.get("#{@nc_api_url}/v1/nodes/#{node_name}") 16 | JSON.parse(node_res.body) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/puppetclassify/rules.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | require 'json' 3 | 4 | class Rules 5 | def initialize(nc_api_url, puppet_https) 6 | @nc_api_url = nc_api_url 7 | @puppet_https = puppet_https 8 | end 9 | 10 | def translate(rule) 11 | rules_res = @puppet_https.post("#{@nc_api_url}/v1/rules/translate", rule.to_json) 12 | 13 | unless rules_res.code.to_i == 200 14 | STDERR.puts "There was a problem with your rule: HTTP #{rules_res.code.to_i} #{rules_res.message}" 15 | STDERR.puts rules_res.body 16 | else 17 | JSON.parse(rules_res.body) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/puppetclassify/update_classes.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | 3 | class UpdateClasses 4 | def initialize(nc_api_url, puppet_https) 5 | @nc_api_url = nc_api_url 6 | @puppet_https = puppet_https 7 | end 8 | 9 | def update 10 | update_res = @puppet_https.post("#{@nc_api_url}/v1/update-classes") 11 | 12 | unless update_res.code.to_i == 201 13 | STDERR.puts "An error has occurred during the update: HTTP #{update_res.code} #{update_res.message}" 14 | STDERR.puts update_res.body 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/puppetclassify/validate.rb: -------------------------------------------------------------------------------- 1 | require 'puppet_https' 2 | require 'json' 3 | 4 | class Validate 5 | def initialize(nc_api_url, puppet_https) 6 | @nc_api_url = nc_api_url 7 | @puppet_https = puppet_https 8 | end 9 | 10 | def validate_group(group_info) 11 | validate_res = @puppet_https.post("#{@nc_api_url}/v1/validate/group", group_info.to_json) 12 | 13 | unless validate_res.code.to_i == 200 14 | STDERR.puts "An error has occured validating the group: HTTP #{validate_res.code} #{validate_res.message}" 15 | STDERR.puts validate_res.body 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /puppetclassify.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'puppetclassify' 3 | s.version = '0.2.0' 4 | s.date = '2024-09-05' 5 | s.summary = 'Puppet Classify!' 6 | s.description = 'A ruby library to interface with the classifier service' 7 | s.authors = ['Puppet Labs'] 8 | s.email = 'info@puppet.com' 9 | s.files = Dir['README.md', 'lib/**/*'] 10 | s.license = 'MIT' 11 | s.homepage = 'https://github.com/puppetlabs/puppet-classify' 12 | end 13 | -------------------------------------------------------------------------------- /spec/puppet_https_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../lib/puppet_https' 3 | 4 | describe PuppetHttps do 5 | describe "with specified certificate auth" do 6 | before :each do 7 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 8 | 9 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 10 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 11 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 12 | 13 | auth_info = { 14 | "private_key_path" => private_key_path, 15 | "certificate_path" => certificate_path, 16 | "ca_certificate_path" => ca_certificate_path, 17 | } 18 | 19 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 20 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 21 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 22 | 23 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 24 | expect(File).to receive("read").with(private_key_path).and_return('a key') 25 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 26 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 27 | @puppet_https = PuppetHttps.new(auth_info) 28 | end 29 | 30 | describe "#new" do 31 | it "takes an auth hash and returns a PuppetHttps object" do 32 | expect(@puppet_https).to be_an_instance_of PuppetHttps 33 | expect(@puppet_https).to have_attributes( 34 | :auth_method => 'cert', 35 | ) 36 | end 37 | end 38 | end 39 | 40 | describe "with token auth" do 41 | describe "with passed token" do 42 | before :each do 43 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 44 | 45 | auth_info = { 46 | "ca_certificate_path" => "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem", 47 | "token" => "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 48 | } 49 | 50 | @puppet_https = PuppetHttps.new(auth_info) 51 | end 52 | 53 | describe "#new" do 54 | it "takes an auth hash and returns a PuppetHttps object" do 55 | expect(@puppet_https).to be_an_instance_of PuppetHttps 56 | expect(@puppet_https).to have_attributes( 57 | :auth_method => 'token', 58 | :token => "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 59 | ) 60 | end 61 | end 62 | 63 | end 64 | 65 | describe "with passed empty token" do 66 | it "detects the empty token" do 67 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 68 | 69 | auth_info = { 70 | "ca_certificate_path" => "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem", 71 | "token" => "", 72 | } 73 | 74 | expect { @puppet_https = PuppetHttps.new(auth_info) }.to raise_error(RuntimeError, /Received an empty string for token/) 75 | end 76 | end 77 | 78 | describe "with valid token path" do 79 | it "takes an auth hash and returns a PuppetHttps object" do 80 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 81 | 82 | auth_info = { 83 | "ca_certificate_path" => "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem", 84 | "token_path" => "/home/foo/.puppetlabs/token", 85 | } 86 | 87 | expect(File).to receive(:exist?).with("/home/foo/.puppetlabs/token").and_return(true) 88 | expect(File).to receive(:zero?).with("/home/foo/.puppetlabs/token").and_return(false) 89 | expect(File).to receive(:exist?).with(auth_info['ca_certificate_path']).and_return(true) 90 | 91 | @puppet_https = PuppetHttps.new(auth_info) 92 | expect(@puppet_https).to be_an_instance_of PuppetHttps 93 | expect(@puppet_https).to have_attributes( 94 | :auth_method => 'token', 95 | :token_path => "/home/foo/.puppetlabs/token", 96 | ) 97 | end 98 | end 99 | 100 | describe "with token path specified for a non existant file" do 101 | it "detects the missing token file" do 102 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 103 | 104 | auth_info = { 105 | "token_path" => "/no/such/path", 106 | } 107 | expect(File).to receive(:exist?).with("/no/such/path").and_return(false) 108 | expect { @puppet_https = PuppetHttps.new(auth_info) }.to raise_error(RuntimeError, 'Token file not found at [/no/such/path]') 109 | end 110 | end 111 | end 112 | 113 | describe "with no auth method specified" do 114 | default_token_path = File.join('/homie/foo', '.puppetlabs', 'token') 115 | 116 | describe "and a token file at the default location" do 117 | it "uses the token file from the default location" do 118 | allow(ENV).to receive(:[]).with("HOME").and_return("/homie/foo") 119 | expect(File).to receive("exist?").with(default_token_path).twice.and_return(true) 120 | @puppet_https = PuppetHttps.new({}) 121 | expect(@puppet_https).to be_an_instance_of PuppetHttps 122 | expect(@puppet_https).to have_attributes( 123 | :auth_method => 'token', 124 | :token_path => default_token_path, 125 | ) 126 | end 127 | end 128 | 129 | describe "and an empty token file at the default location" do 130 | 131 | it "raises an exception" do 132 | allow(ENV).to receive(:[]).with("HOME").and_return("/homie/foo") 133 | expect(File).to receive("exist?").twice.with(default_token_path).and_return(true) 134 | expect(File).to receive("zero?").with(default_token_path).and_return(true) 135 | 136 | expect { @puppet_https = PuppetHttps.new({}) }.to raise_error(RuntimeError, "Token file at [#{default_token_path}] is empty") 137 | end 138 | end 139 | 140 | describe "and no token file at the default location" do 141 | it "raises an exception" do 142 | allow(ENV).to receive(:[]).with("HOME").and_return("/homie/foo") 143 | expect(File).to receive("exist?").with(default_token_path).and_return(false) 144 | expect { @puppet_https = PuppetHttps.new({}) }.to raise_error(RuntimeError, /No authentication methods available/) 145 | end 146 | end 147 | end 148 | 149 | 150 | end 151 | -------------------------------------------------------------------------------- /spec/puppetclassify/classes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../lib/puppetclassify/classes.rb' 3 | require_relative '../../lib/puppetclassify' 4 | 5 | describe Classes do 6 | before :each do 7 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 8 | 9 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 10 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 11 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 12 | 13 | auth_info = { 14 | "private_key_path" => private_key_path, 15 | "certificate_path" => certificate_path, 16 | "ca_certificate_path" => ca_certificate_path, 17 | } 18 | 19 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 20 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 21 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 22 | 23 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 24 | expect(File).to receive("read").with(private_key_path).and_return('a key') 25 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 26 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 27 | 28 | @puppetclassify = PuppetClassify.new(@classifier_url, auth_info) 29 | 30 | @sample_classes = "[{\"parameters\":{},\"environment\":\"production\",\"name\":\"pe_repo::platform::solaris_11_i386\"},{\"parameters\":{},\"environment\":\"production\",\"name\":\"pe_postgresql::server::passwd\"},{\"parameters\":{},\"environment\":\"production\",\"name\":\"puppet_enterprise::profile::certificate_authority\"},{\"parameters\":{},\"environment\":\"production\",\"name\":\"pe_repo::platform::el_4_i386\"},{\"parameters\":{},\"environment\":\"production\",\"name\":\"pe_repo::platform::ubuntu_1004_i386\"},{\"parameters\":{},\"environment\":\"production\",\"name\":\"pe_repo::platform::osx_109_x86_64\"}]" 31 | end 32 | 33 | describe "#get_classes" do 34 | it "gets an array of all of the classes" do 35 | stub_request(:get, "#{@classifier_url}/v1/classes"). 36 | with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). 37 | to_return(:status => 200, :body => @sample_classes, :headers => {}) 38 | expect(@puppetclassify.classes.get_classes).to be_an_instance_of Array 39 | end 40 | end 41 | 42 | describe "#get_environment_classes" do 43 | let(:environment_name) { environment_name = 'production' } 44 | 45 | it "gets an array of all the classes in a specific environment" do 46 | stub_request(:get, "#{@classifier_url}/v1/environments/#{environment_name}/classes"). 47 | with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). 48 | to_return(:status => 200, :body => @sample_classes, :headers => {}) 49 | expect(@puppetclassify.classes.get_environment_classes(environment_name)).to be_an_instance_of Array 50 | end 51 | end 52 | 53 | describe "#get_environment_class" do 54 | let(:environment_name) { environment_name = 'production' } 55 | let(:class_name) { class_name = 'puppet_enterprise::profile::certificate_authority' } 56 | let(:response_body) { response_body = "{\"parameters\":{},\"environment\":\"production\",\"name\":\"puppet_enterprise::profile::certificate_authority\"}" } 57 | 58 | it "gets a class from a specific environment" do 59 | stub_request(:get, "#{@classifier_url}/v1/environments/#{environment_name}/classes/#{class_name}"). 60 | with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). 61 | to_return(:status => 200, :body => response_body, :headers => {}) 62 | expect(@puppetclassify.classes.get_environment_class(environment_name, class_name)).to be_an_instance_of Hash 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/puppetclassify/commands_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../lib/puppetclassify' 3 | 4 | describe Commands do 5 | before :each do 6 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 7 | 8 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 9 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 10 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 11 | 12 | auth_info = { 13 | "private_key_path" => private_key_path, 14 | "certificate_path" => certificate_path, 15 | "ca_certificate_path" => ca_certificate_path, 16 | } 17 | 18 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 19 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 20 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 21 | 22 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 23 | expect(File).to receive("read").with(private_key_path).and_return('a key') 24 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 25 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 26 | 27 | @puppetclassify = PuppetClassify.new(@classifier_url, auth_info) 28 | end 29 | 30 | describe "#unpin_from_all" do 31 | let(:nodes) { nodes = ["foo", "bar"] } 32 | let(:resp_body) { resp_body = "{\"nodes\": [{\"name\": \"foo\", \"groups\": [{\"id\": \"8310b045-c244-4008-88d0-b49573c84d2d\", \"name\": \"Webservers\", \"environment\": \"production\"}, {\"id\": \"84b19b51-6db5-4897-9409-a4a3a94b7f09\", \"name\": \"Test\", \"environment\": \"test\"}]}, {\"name\": \"bar\", \"groups\": [{\"id\": \"84b19b51-6db5-4897-9409-a4a3a94b7f09\", \"name\": \"Test\", \"environment\": \"test\"}]}]}" } 33 | 34 | it "unpins all given nodes from all groups" do 35 | stub_request(:post, "#{@classifier_url}/v1/commands/unpin-from-all"). 36 | with(:body => "{\"nodes\":[\"foo\",\"bar\"]}", 37 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 38 | to_return(:status => 200, :body => resp_body, :headers => {}) 39 | expect(@puppetclassify.commands.unpin_from_all(nodes)).to be_an_instance_of Hash 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/puppetclassify/environments_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../lib/puppetclassify' 3 | 4 | describe Environments do 5 | before :each do 6 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 7 | 8 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 9 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 10 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 11 | 12 | auth_info = { 13 | "private_key_path" => private_key_path, 14 | "certificate_path" => certificate_path, 15 | "ca_certificate_path" => ca_certificate_path, 16 | } 17 | 18 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 19 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 20 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 21 | 22 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 23 | expect(File).to receive("read").with(private_key_path).and_return('a key') 24 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 25 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 26 | 27 | @puppetclassify = PuppetClassify.new(@classifier_url, auth_info) 28 | end 29 | 30 | describe "#get_environments" do 31 | let(:response_body) { response_body = "[{\"name\":\"production\"}]" } 32 | 33 | it "returns an Array of environments" do 34 | stub_request(:get, "#{@classifier_url}/v1/environments"). 35 | with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). 36 | to_return(:status => 200, :body => response_body, :headers => {}) 37 | expect(@puppetclassify.environments.get_environments).to be_an_instance_of Array 38 | end 39 | end 40 | 41 | describe "#get_environment" do 42 | let(:response_body) { response_body = "{\"name\":\"production\"}" } 43 | let(:name) { name = 'production' } 44 | 45 | it "returns a Hash of the specified environment" do 46 | stub_request(:get, "#{@classifier_url}/v1/environments/#{name}"). 47 | with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). 48 | to_return(:status => 200, :body => response_body, :headers => {}) 49 | expect(@puppetclassify.environments.get_environment(name)).to be_an_instance_of Hash 50 | end 51 | end 52 | 53 | describe "#create_environment" do 54 | let(:name) { name = 'development' } 55 | 56 | it "creates an environment given a name" do 57 | stub_request(:put, "#{@classifier_url}/v1/environments/#{name}"). 58 | with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 59 | to_return(:status => 201, :body => "", :headers => {}) 60 | expect(@puppetclassify.environments.create_environment(name)).to be_nil 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/puppetclassify/groups_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../lib/puppetclassify' 3 | 4 | describe Groups do 5 | before :each do 6 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 7 | 8 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 9 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 10 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 11 | 12 | auth_info = { 13 | "private_key_path" => private_key_path, 14 | "certificate_path" => certificate_path, 15 | "ca_certificate_path" => ca_certificate_path, 16 | } 17 | 18 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 19 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 20 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 21 | 22 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 23 | expect(File).to receive("read").with(private_key_path).and_return('a key') 24 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 25 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 26 | 27 | @puppetclassify = PuppetClassify.new(@classifier_url, auth_info) 28 | 29 | @simple_response_body = "{\"environment_trumps\":false,\"parent\":\"96d58c24-3cc5-4b07-bd36-44e0932bb041\",\"name\":\"PE Master\",\"rule\":[\"or\",[\"=\",\"name\",\"puppetmaster.local\"]],\"variables\":{},\"id\":\"d8fd0add-4671-4cc7-92a5-d6e58bb7898d\",\"environment\":\"production\",\"classes\":{\"puppet_enterprise::profile::master::mcollective\":{},\"puppet_enterprise::profile::mcollective::peadmin\":{},\"puppet_enterprise::profile::master\":{},\"pe_repo\":{},\"pe_repo::platform::el_6_x86_64\":{}}}" 30 | 31 | end 32 | 33 | describe "#newobject" do 34 | it "takes a classifier url and auth info hash and returns a PuppetClassify object" do 35 | expect(@puppetclassify.groups).to be_an_instance_of Groups 36 | end 37 | end 38 | 39 | describe "#get_groups" do 40 | it "returns a string of groups" do 41 | stub_request(:get, "#{@classifier_url}/v1/groups"). 42 | with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). 43 | to_return(:status => 200, :body => @simple_response_body, :headers => {}) 44 | expect(@puppetclassify.groups.get_groups).to be_an_instance_of Hash 45 | end 46 | end 47 | 48 | describe "#get_group" do 49 | it "returns a group given an ID" do 50 | stub_request(:get, "#{@classifier_url}/v1/groups/96d58c24-3cc5-4b07-bd36-44e0932bb041"). 51 | with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). 52 | to_return(:status => 200, :body => @simple_response_body, :headers => {}) 53 | expect(@puppetclassify.groups.get_group('96d58c24-3cc5-4b07-bd36-44e0932bb041')).to be_an_instance_of Hash 54 | end 55 | 56 | it "returns nil if no group can be found given an invalid ID" do 57 | stub_request(:get, "#{@classifier_url}/v1/groups/fc500c43-5065-469b-91fc-37ed0e500e81"). 58 | with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). 59 | to_return(:status => 404, :headers => {}) 60 | expect(@puppetclassify.groups.get_group('fc500c43-5065-469b-91fc-37ed0e500e81')).to be_nil 61 | end 62 | end 63 | 64 | describe "#get_group_id" do 65 | it "returns a group id given a group name" do 66 | end 67 | end 68 | 69 | describe "#create_group" do 70 | let(:example_group) { example_group = {"name"=>"examplegroup", "description"=>"A cool group", "environment"=>"production", "parent"=>"00000000-0000-4000-8000-000000000000", "classes"=>{}} } 71 | 72 | let(:group_with_id) { group_with_id = {"name"=>"groupwithid", "description"=>"A cool group", "environment"=>"production", "parent"=>"00000000-0000-4000-8000-000000000000", "classes"=>{}, "id"=>"fc500c43-5065-469b-91fc-37ed0e500e81"} } 73 | 74 | it "creates a group with a specified id" do 75 | stub_request(:put, "#{@classifier_url}/v1/groups/#{group_with_id['id']}"). 76 | with(:body => group_with_id, 77 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 78 | to_return(:status => 303, :body => "", :headers => {}) 79 | expect(@puppetclassify.groups.create_group(group_with_id)).to be_an_instance_of String 80 | end 81 | 82 | it "creates a group without a specified id" do 83 | stub_request(:post, "#{@classifier_url}/v1/groups"). 84 | with(:body => example_group, 85 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 86 | to_return(:status => 303, :body => "", :headers => {:location => "/classifier-api/v1/groups/777fa762-4cbc-4dff-af6b-38cc32acbca0"}) 87 | expect(@puppetclassify.groups.create_group(example_group)).to be_an_instance_of String 88 | end 89 | end 90 | 91 | describe "#update_group" do 92 | let(:example_group_delta) { example_group_delta = {"description"=>"A super cool group", "id"=>"fc500c43-5065-469b-91fc-37ed0e500e81"} } 93 | 94 | it "updates a group given a delta" do 95 | stub_request(:post, "#{@classifier_url}/v1/groups/#{example_group_delta['id']}"). 96 | with(:body => example_group_delta, 97 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 98 | to_return(:status => 200, :body => "", :headers => {}) 99 | expect(@puppetclassify.groups.update_group(example_group_delta)).to be_nil 100 | end 101 | end 102 | 103 | describe "#delete_group" do 104 | let(:id) { id = "fc500c43-5065-469b-91fc-37ed0e500e81" } 105 | 106 | it "deletes a group given an ID" do 107 | stub_request(:delete, "#{@classifier_url}/v1/groups/#{id}"). 108 | with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 109 | to_return(:status => 204, :body => "", :headers => {}) 110 | expect(@puppetclassify.groups.delete_group(id)).to be_nil 111 | end 112 | end 113 | 114 | describe "#pin_nodes" do 115 | let(:id) { id = "fc500c43-5065-469b-91fc-37ed0e500e81" } 116 | let(:nodes) { nodes = ["foo", "bar", "baz"] } 117 | 118 | it "pins a node to a given group" do 119 | stub_request(:post, "#{@classifier_url}/v1/groups/#{id}/pin"). 120 | with(:body => "{\"nodes\":[\"foo\",\"bar\",\"baz\"]}", 121 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 122 | to_return(:status => 204, :body => "", :headers => {}) 123 | expect(@puppetclassify.groups.pin_nodes(id, nodes)).to be_nil 124 | end 125 | end 126 | 127 | describe "#unpin_nodes" do 128 | let(:id) { id = "fc500c43-5065-469b-91fc-37ed0e500e81" } 129 | let(:nodes) { nodes = ["foo", "bar", "baz"] } 130 | 131 | it "pins a node to a given group" do 132 | stub_request(:post, "#{@classifier_url}/v1/groups/#{id}/unpin"). 133 | with(:body => "{\"nodes\":[\"foo\",\"bar\",\"baz\"]}", 134 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 135 | to_return(:status => 204, :body => "", :headers => {}) 136 | expect(@puppetclassify.groups.unpin_nodes(id, nodes)).to be_nil 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /spec/puppetclassify/import_hierarchy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../lib/puppetclassify' 3 | 4 | describe ImportHierarchy do 5 | before :each do 6 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 7 | 8 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 9 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 10 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 11 | 12 | auth_info = { 13 | "private_key_path" => private_key_path, 14 | "certificate_path" => certificate_path, 15 | "ca_certificate_path" => ca_certificate_path, 16 | } 17 | 18 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 19 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 20 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 21 | 22 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 23 | expect(File).to receive("read").with(private_key_path).and_return('a key') 24 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 25 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 26 | 27 | @puppetclassify = PuppetClassify.new(@classifier_url, auth_info) 28 | 29 | @import_group_hash = [{"environment_trumps"=>false, "parent"=>"00000000-0000-4000-8000-000000000000", "name"=>"default", "rule"=>["and", ["~", "name", ".*"]], "variables"=>{}, "id"=>"00000000-0000-4000-8000-000000000000", "environment"=>"production", "classes"=>{}},{"name"=>"testgroup", "description"=>"A cool group", "environment"=>"production", "parent"=>"00000000-0000-4000-8000-000000000000", "classes"=>{}}] 30 | 31 | end 32 | 33 | describe "#import" do 34 | it "sends a group hash to be imported" do 35 | stub_request(:post, "#{@classifier_url}/v1/import-hierarchy"). 36 | with(:body => @import_group_hash.to_json, 37 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 38 | to_return(:status => 204, :body => "", :headers => {}) 39 | expect(@puppetclassify.import_hierarchy.import(@import_group_hash)).to be_nil 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/puppetclassify/last_class_update_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../lib/puppetclassify/last_class_update.rb' 3 | require_relative '../../lib/puppetclassify' 4 | 5 | describe LastClassUpdate do 6 | before :each do 7 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 8 | 9 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 10 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 11 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 12 | 13 | auth_info = { 14 | "private_key_path" => private_key_path, 15 | "certificate_path" => certificate_path, 16 | "ca_certificate_path" => ca_certificate_path, 17 | } 18 | 19 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 20 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 21 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 22 | 23 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 24 | expect(File).to receive("read").with(private_key_path).and_return('a key') 25 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 26 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 27 | 28 | @puppetclassify = PuppetClassify.new(@classifier_url, auth_info) 29 | end 30 | 31 | describe "#get" do 32 | let(:response_body) { response_body = {"last_update"=>"2014-11-07T17:38:44.805Z"} } 33 | it "returns gives a response for last class update" do 34 | stub_request(:get, "#{@classifier_url}/v1/last-class-update"). 35 | with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). 36 | to_return(:status => 200, :body => response_body.to_json, :headers => {}) 37 | expect(@puppetclassify.last_class_update.get).to be_an_instance_of Hash 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/puppetclassify/nodes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../lib/puppetclassify' 3 | 4 | describe Nodes do 5 | before :each do 6 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 7 | 8 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 9 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 10 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 11 | 12 | auth_info = { 13 | "private_key_path" => private_key_path, 14 | "certificate_path" => certificate_path, 15 | "ca_certificate_path" => ca_certificate_path, 16 | } 17 | 18 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 19 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 20 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 21 | 22 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 23 | expect(File).to receive("read").with(private_key_path).and_return('a key') 24 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 25 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 26 | 27 | @puppetclassify = PuppetClassify.new(@classifier_url, auth_info) 28 | end 29 | 30 | describe "#get_nodes" do 31 | end 32 | 33 | describe "#get_node" do 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/puppetclassify/rules_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../lib/puppetclassify' 3 | 4 | describe Rules do 5 | before :each do 6 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 7 | 8 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 9 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 10 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 11 | 12 | auth_info = { 13 | "private_key_path" => private_key_path, 14 | "certificate_path" => certificate_path, 15 | "ca_certificate_path" => ca_certificate_path, 16 | } 17 | 18 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 19 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 20 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 21 | 22 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 23 | expect(File).to receive("read").with(private_key_path).and_return('a key') 24 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 25 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 26 | 27 | @puppetclassify = PuppetClassify.new(@classifier_url, auth_info) 28 | end 29 | 30 | describe "#translate" do 31 | let(:rule) { rule = ["or",["=","name","puppetmaster.local"]] } 32 | let(:response_body) { response_body = "[\"or\",[\"=\",\"name\",\"puppetmaster.local\"]]" } 33 | 34 | it "gets an array of a translated rule" do 35 | stub_request(:post, "#{@classifier_url}/v1/rules/translate"). 36 | with(:body => rule.to_json, 37 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 38 | to_return(:status => 200, :body => response_body, :headers => {}) 39 | expect(@puppetclassify.rules.translate(rule)).to be_an_instance_of Array 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/puppetclassify/update_classes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../lib/puppetclassify' 3 | 4 | describe UpdateClasses do 5 | before :each do 6 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 7 | 8 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 9 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 10 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 11 | 12 | auth_info = { 13 | "private_key_path" => private_key_path, 14 | "certificate_path" => certificate_path, 15 | "ca_certificate_path" => ca_certificate_path, 16 | } 17 | 18 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 19 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 20 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 21 | 22 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 23 | expect(File).to receive("read").with(private_key_path).and_return('a key') 24 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 25 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 26 | 27 | @puppetclassify = PuppetClassify.new(@classifier_url, auth_info) 28 | end 29 | 30 | describe "#update" do 31 | it "sends an update message to the classifier" do 32 | stub_request(:post, "#{@classifier_url}/v1/update-classes"). 33 | with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 34 | to_return(:status => 201, :body => "", :headers => {}) 35 | expect(@puppetclassify.update_classes.update).to be_nil 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/puppetclassify/validate_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../lib/puppetclassify' 3 | 4 | describe Validate do 5 | before :each do 6 | @classifier_url = 'https://puppetmaster.local:4433/classifier-api' 7 | 8 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 9 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 10 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 11 | 12 | auth_info = { 13 | "private_key_path" => private_key_path, 14 | "certificate_path" => certificate_path, 15 | "ca_certificate_path" => ca_certificate_path, 16 | } 17 | 18 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 19 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 20 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 21 | 22 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 23 | expect(File).to receive("read").with(private_key_path).and_return('a key') 24 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 25 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 26 | 27 | @puppetclassify = PuppetClassify.new(@classifier_url, auth_info) 28 | end 29 | 30 | describe "#validate" do 31 | let(:group) { group = {"name"=>"testgroup", "description"=>"A cool group", "environment"=>"production", "parent"=>"00000000-0000-4000-8000-000000000000", "classes"=>{}} } 32 | 33 | it "sends a group to be validated" do 34 | stub_request(:post, "#{@classifier_url}/v1/validate/group"). 35 | with(:body => group, 36 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby'}). 37 | to_return(:status => 200, :body => "", :headers => {}) 38 | expect(@puppetclassify.validate.validate_group(group)).to be_nil 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/puppetclassify_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../lib/puppetclassify' 3 | 4 | describe PuppetClassify do 5 | before :each do 6 | classifier_url = 'https://puppetmaster.local:4433/classifier-api' 7 | 8 | private_key_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.private_key.pem" 9 | certificate_path = "/opt/puppet/share/puppet-dashboard/certs/pe-internal-dashboard.cert.pem" 10 | ca_certificate_path = "/opt/puppet/share/puppet-dashboard/certs/ca_cert.pem" 11 | 12 | auth_info = { 13 | "private_key_path" => private_key_path, 14 | "certificate_path" => certificate_path, 15 | "ca_certificate_path" => ca_certificate_path, 16 | } 17 | 18 | expect(File).to receive("exist?").with(certificate_path).and_return(true) 19 | expect(File).to receive("exist?").with(private_key_path).and_return(true) 20 | expect(File).to receive("exist?").with(ca_certificate_path).and_return(true) 21 | 22 | expect(File).to receive("read").with(certificate_path).and_return('a cert') 23 | expect(File).to receive("read").with(private_key_path).and_return('a key') 24 | expect(OpenSSL::X509::Certificate).to receive("new").with('a cert').and_return('a cert object') 25 | expect(OpenSSL::PKey::RSA).to receive("new").with('a key').and_return('a key object') 26 | 27 | @puppetclassify = PuppetClassify.new(classifier_url, auth_info) 28 | end 29 | 30 | describe "#newobject" do 31 | it "takes a classifier url and auth info hash and returns a PuppetClassify object" do 32 | expect(@puppetclassify).to be_an_instance_of PuppetClassify 33 | end 34 | 35 | it "has methods with objects that are not nil" do 36 | expect(@puppetclassify.groups).to be_truthy 37 | expect(@puppetclassify.nodes).to be_truthy 38 | expect(@puppetclassify.environments).to be_truthy 39 | expect(@puppetclassify.classes).to be_truthy 40 | expect(@puppetclassify.import_hierarchy).to be_truthy 41 | expect(@puppetclassify.update_classes).to be_truthy 42 | expect(@puppetclassify.validate).to be_truthy 43 | expect(@puppetclassify.rules).to be_truthy 44 | expect(@puppetclassify.last_class_update).to be_truthy 45 | expect(@puppetclassify.classification).to be_truthy 46 | end 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'puppetclassify' 2 | require 'webmock/rspec' 3 | --------------------------------------------------------------------------------