quixoten-puppetdb-terminus 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,113 @@
1
+ require 'puppet/error'
2
+ require 'puppet/network/http_pool'
3
+ require 'puppet/util/puppetdb'
4
+ require 'puppet/util/puppetdb/command_names'
5
+ require 'puppet/util/puppetdb/char_encoding'
6
+ require 'json'
7
+
8
+ class Puppet::Util::Puppetdb::Command
9
+ include Puppet::Util::Puppetdb
10
+ include Puppet::Util::Puppetdb::CommandNames
11
+
12
+ Url = "/v3/commands"
13
+
14
+ # Public instance methods
15
+
16
+ # Initialize a Command object, for later submission.
17
+ #
18
+ # @param command String the name of the command; should be one of the
19
+ # constants defined in `Puppet::Util::Puppetdb::CommandNames`
20
+ # @param version Integer the command version number
21
+ # @param certname The certname that this command operates on (is not
22
+ # included in the actual submission)
23
+ # @param payload Object the payload of the command. This object should be a
24
+ # primitive (numeric type, string, array, or hash) that is natively supported
25
+ # by JSON serialization / deserialization libraries.
26
+ def initialize(command, version, certname, payload)
27
+ @command = command
28
+ @version = version
29
+ @certname = certname
30
+ profile "Format payload" do
31
+ @payload = self.class.format_payload(command, version, payload)
32
+ end
33
+ end
34
+
35
+ attr_reader :command, :version, :certname, :payload
36
+
37
+ # Submit the command, returning the result hash.
38
+ #
39
+ # @return [Hash <String, String>]
40
+ def submit
41
+ checksum = Digest::SHA1.hexdigest(payload)
42
+
43
+ for_whom = " for #{certname}" if certname
44
+
45
+ begin
46
+ response = profile "Submit command HTTP post" do
47
+ http = Puppet::Network::HttpPool.http_instance(config.server, config.port)
48
+ http.post(Url + "?checksum=#{checksum}", payload, headers)
49
+ end
50
+
51
+ Puppet::Util::Puppetdb.log_x_deprecation_header(response)
52
+
53
+ if response.is_a? Net::HTTPSuccess
54
+ result = JSON.parse(response.body)
55
+ Puppet.info "'#{command}' command#{for_whom} submitted to PuppetDB with UUID #{result['uuid']}"
56
+ result
57
+ else
58
+ # Newline characters cause an HTTP error, so strip them
59
+ error = "[#{response.code} #{response.message}] #{response.body.gsub(/[\r\n]/, '')}"
60
+ if config.soft_write_failure
61
+ Puppet.err "'#{command}'command#{for_whom} failed during submission to PuppetDB: #{error}"
62
+ else
63
+ raise Puppet::Error, error
64
+ end
65
+ end
66
+ rescue => e
67
+ error = "Failed to submit '#{command}' command#{for_whom} to PuppetDB at #{config.server}:#{config.port}: #{e}"
68
+ if config.soft_write_failure
69
+ Puppet.err error
70
+ else
71
+ # TODO: Use new exception handling methods from Puppet 3.0 here as soon as
72
+ # we are able to do so (can't call them yet w/o breaking backwards
73
+ # compatibility.) We should either be using a nested exception or calling
74
+ # Puppet::Util::Logging#log_exception or #log_and_raise here; w/o them
75
+ # we lose context as to where the original exception occurred.
76
+ puts e, e.backtrace if Puppet[:trace]
77
+ raise Puppet::Error, error
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+ # @!group Private class methods
84
+
85
+ # @api private
86
+ def self.format_payload(command, version, payload)
87
+ message = {
88
+ :command => command,
89
+ :version => version,
90
+ :payload => payload,
91
+ }.to_pson
92
+
93
+ Puppet::Util::Puppetdb::CharEncoding.utf8_string(message)
94
+ end
95
+
96
+ # @!group Private instance methods
97
+
98
+ # @api private
99
+ def headers
100
+ {
101
+ "Accept" => "application/json",
102
+ "Content-Type" => "application/json",
103
+ }
104
+ end
105
+
106
+ # @api private
107
+ def config
108
+ # Would prefer to pass this to the constructor or acquire it some other
109
+ # way besides this pseudo-global reference.
110
+ Puppet::Util::Puppetdb.config
111
+ end
112
+
113
+ end
@@ -0,0 +1,8 @@
1
+ module Puppet::Util::Puppetdb
2
+ module CommandNames
3
+ CommandReplaceCatalog = "replace catalog"
4
+ CommandReplaceFacts = "replace facts"
5
+ CommandDeactivateNode = "deactivate node"
6
+ CommandStoreReport = "store report"
7
+ end
8
+ end
@@ -0,0 +1,112 @@
1
+ require 'puppet/util/puppetdb/command_names'
2
+ require 'puppet/util/puppetdb/blacklist'
3
+
4
+ module Puppet::Util::Puppetdb
5
+ class Config
6
+ include Puppet::Util::Puppetdb::CommandNames
7
+
8
+ # Public class methods
9
+
10
+ def self.load(config_file = nil)
11
+ defaults = {
12
+ :server => "puppetdb",
13
+ :port => 8081,
14
+ :soft_write_failure => false,
15
+ :ignore_blacklisted_events => true,
16
+ }
17
+
18
+ config_file ||= File.join(Puppet[:confdir], "puppetdb.conf")
19
+
20
+ if File.exists?(config_file)
21
+ Puppet.debug("Configuring PuppetDB terminuses with config file #{config_file}")
22
+ content = File.read(config_file)
23
+ else
24
+ Puppet.debug("No #{config_file} file found; falling back to default server and port #{defaults[:server]}:#{defaults[:port]}")
25
+ content = ''
26
+ end
27
+
28
+ result = {}
29
+ section = nil
30
+ content.lines.each_with_index do |line,number|
31
+ # Gotta track the line numbers properly
32
+ number += 1
33
+ case line
34
+ when /^\[(\w+)\s*\]$/
35
+ section = $1
36
+ result[section] ||= {}
37
+ when /^\s*(\w+)\s*=\s*(\S+)\s*$/
38
+ raise "Setting '#{line}' is illegal outside of section in PuppetDB config #{config_file}:#{number}" unless section
39
+ result[section][$1] = $2
40
+ when /^\s*[#;]/
41
+ # Skip comments
42
+ when /^\s*$/
43
+ # Skip blank lines
44
+ else
45
+ raise "Unparseable line '#{line}' in PuppetDB config #{config_file}:#{number}"
46
+ end
47
+ end
48
+
49
+
50
+ main_section = result['main'] || {}
51
+ # symbolize the keys
52
+ main_section = main_section.inject({}) {|h, (k,v)| h[k.to_sym] = v ; h}
53
+ # merge with defaults but filter out anything except the legal settings
54
+ config_hash = defaults.merge(main_section).reject do |k, v|
55
+ !([:server, :port, :ignore_blacklisted_events, :soft_write_failure].include?(k))
56
+ end
57
+
58
+ config_hash[:server] = config_hash[:server].strip
59
+ config_hash[:port] = config_hash[:port].to_i
60
+ config_hash[:ignore_blacklisted_events] =
61
+ Puppet::Util::Puppetdb.to_bool(config_hash[:ignore_blacklisted_events])
62
+ config_hash[:soft_write_failure] =
63
+ Puppet::Util::Puppetdb.to_bool(config_hash[:soft_write_failure])
64
+
65
+ self.new(config_hash)
66
+ rescue => detail
67
+ puts detail.backtrace if Puppet[:trace]
68
+ Puppet.warning "Could not configure PuppetDB terminuses: #{detail}"
69
+ raise
70
+ end
71
+
72
+ # @!group Public instance methods
73
+
74
+ def initialize(config_hash = {})
75
+ @config = config_hash
76
+ initialize_blacklisted_events()
77
+ end
78
+
79
+ def server
80
+ config[:server]
81
+ end
82
+
83
+ def port
84
+ config[:port]
85
+ end
86
+
87
+ def ignore_blacklisted_events?
88
+ config[:ignore_blacklisted_events]
89
+ end
90
+
91
+ def is_event_blacklisted?(event)
92
+ @blacklist.is_event_blacklisted? event
93
+ end
94
+
95
+ def soft_write_failure
96
+ config[:soft_write_failure]
97
+ end
98
+
99
+ # @!group Private instance methods
100
+
101
+ # @!attribute [r] count
102
+ # @api private
103
+ attr_reader :config
104
+
105
+ Blacklist = Puppet::Util::Puppetdb::Blacklist
106
+
107
+ # @api private
108
+ def initialize_blacklisted_events(events = Blacklist::BlacklistedEvents)
109
+ @blacklist = Blacklist.new(events)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,31 @@
1
+ require 'semver'
2
+ require 'puppet/version'
3
+ require 'puppet/error'
4
+
5
+ module Puppet::Util::Puppetdb
6
+ # Global checks for version support and other validations before the terminus
7
+ # is used.
8
+ #
9
+ class GlobalCheck
10
+ # Validate that the support for the version of Puppet we are running on is
11
+ # still maintained.
12
+ #
13
+ # @param minimum [String] minimum version for operation
14
+ # @throws [Puppet::Error] raised if current version is unsupported
15
+ # @api private
16
+ def self.puppet_version_check(minimum)
17
+ minimum_version = ::SemVer.new(minimum)
18
+ puppet_version = ::SemVer.new(Puppet.version)
19
+ if (puppet_version <=> minimum_version) == -1 then
20
+ raise Puppet::Error, "You are attempting to use puppetdb-terminus on an unsupported version of Puppet (#{puppet_version}) the minimum supported version is #{minimum_version}"
21
+ end
22
+ end
23
+
24
+ # Run all checks
25
+ #
26
+ # @throws [Puppet::Error] raised for any validation errors
27
+ def self.run
28
+ self.puppet_version_check("3.5.1")
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,108 @@
1
+ require 'puppet/util'
2
+ require 'puppet/util/logging'
3
+ require 'puppet/util/profiler'
4
+ require 'puppet/util/puppetdb/global_check'
5
+ require 'puppet/util/puppetdb/command_names'
6
+ require 'puppet/util/puppetdb/command'
7
+ require 'puppet/util/puppetdb/config'
8
+ require 'digest/sha1'
9
+ require 'time'
10
+ require 'fileutils'
11
+
12
+ module Puppet::Util::Puppetdb
13
+
14
+ def self.server
15
+ config.server
16
+ end
17
+
18
+ def self.port
19
+ config.port
20
+ end
21
+
22
+ def self.config
23
+ @config ||= Puppet::Util::Puppetdb::Config.load
24
+ @config
25
+ end
26
+
27
+ # This magical stuff is needed so that the indirector termini will make requests to
28
+ # the correct host/port, because this module gets mixed in to our indirector
29
+ # termini.
30
+ module ClassMethods
31
+ def server
32
+ Puppet::Util::Puppetdb.server
33
+ end
34
+
35
+ def port
36
+ Puppet::Util::Puppetdb.port
37
+ end
38
+ end
39
+
40
+ def self.included(child)
41
+ child.extend ClassMethods
42
+ end
43
+
44
+ # Given an instance of ruby's Time class, this method converts it to a String
45
+ # that conforms to PuppetDB's wire format for representing a date/time.
46
+ def self.to_wire_time(time)
47
+ # The current implementation simply calls iso8601, but having this method
48
+ # allows us to change that in the future if needed w/o being forced to
49
+ # update all of the date objects elsewhere in the code.
50
+ time.iso8601(9)
51
+ end
52
+
53
+ # Convert a value (usually a string) to a boolean
54
+ def self.to_bool(value)
55
+ case value
56
+ when true, "true"; return true
57
+ when false, "false"; return false
58
+ else
59
+ raise ArgumentError.new("invalid value for Boolean: \"#{val}\"")
60
+ end
61
+ end
62
+
63
+ # @!group Public instance methods
64
+
65
+ # Submit a command to PuppetDB.
66
+ #
67
+ # @param certname [String] hostname name of puppetdb instance
68
+ # @param payload [String] payload
69
+ # @param command_name [String] name of command
70
+ # @param version [Number] version number of command
71
+ def submit_command(certname, payload, command_name, version)
72
+ profile "Submitted command '#{command_name}' version '#{version}'" do
73
+ command = Puppet::Util::Puppetdb::Command.new(command_name, version, certname, payload)
74
+ command.submit
75
+ end
76
+ end
77
+
78
+ # Profile a block of code and log the time it took to execute.
79
+ #
80
+ # This outputs logs entries to the Puppet masters logging destination
81
+ # providing the time it took, a message describing the profiled code
82
+ # and a leaf location marking where the profile method was called
83
+ # in the profiled hierachy.
84
+ #
85
+ # @param message [String] A description of the profiled event
86
+ # @param block [Block] The segment of code to profile
87
+ # @api public
88
+ def profile(message, &block)
89
+ message = "PuppetDB: " + message
90
+ Puppet::Util::Profiler.profile(message, &block)
91
+ end
92
+
93
+ # @!group Private instance methods
94
+
95
+ # @api private
96
+ def config
97
+ Puppet::Util::Puppetdb.config
98
+ end
99
+
100
+ # @api private
101
+ def log_x_deprecation_header(response)
102
+ if warning = response['x-deprecation']
103
+ Puppet.deprecation_warning "Deprecation from PuppetDB: #{warning}"
104
+ end
105
+ end
106
+ module_function :log_x_deprecation_header
107
+
108
+ end
@@ -0,0 +1,5 @@
1
+ module PuppetDB
2
+ module Terminus
3
+ VERSION = "2.0.0.rc1"
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ require "puppetdb/terminus/version"
2
+
3
+ module PuppetDB
4
+ module Terminus
5
+ end
6
+ end
@@ -0,0 +1 @@
1
+ require 'puppetdb/terminus'
@@ -0,0 +1 @@
1
+ require 'puppetdb-terminus'
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'puppetdb/terminus/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "quixoten-puppetdb-terminus"
8
+ spec.version = PuppetDB::Terminus::VERSION
9
+ spec.authors = ["Devin Christensen"]
10
+ spec.email = ["quixoten@gmail.com"]
11
+ spec.summary = %q{PuppetDB Terminus}
12
+ spec.description = %q{PuppetDB Terminus}
13
+ spec.homepage = "https://github.com/quixoten/puppetdb-terminus"
14
+ spec.license = "Apache 2.0"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quixoten-puppetdb-terminus
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0.rc1
5
+ platform: ruby
6
+ authors:
7
+ - Devin Christensen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: PuppetDB Terminus
42
+ email:
43
+ - quixoten@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - lib/puppet/application/storeconfigs.rb
54
+ - lib/puppet/face/node/deactivate.rb
55
+ - lib/puppet/face/node/status.rb
56
+ - lib/puppet/face/storeconfigs.rb
57
+ - lib/puppet/indirector/catalog/puppetdb.rb
58
+ - lib/puppet/indirector/facts/puppetdb.rb
59
+ - lib/puppet/indirector/facts/puppetdb_apply.rb
60
+ - lib/puppet/indirector/node/puppetdb.rb
61
+ - lib/puppet/indirector/resource/puppetdb.rb
62
+ - lib/puppet/reports/puppetdb.rb
63
+ - lib/puppet/util/puppetdb.rb
64
+ - lib/puppet/util/puppetdb/blacklist.rb
65
+ - lib/puppet/util/puppetdb/char_encoding.rb
66
+ - lib/puppet/util/puppetdb/command.rb
67
+ - lib/puppet/util/puppetdb/command_names.rb
68
+ - lib/puppet/util/puppetdb/config.rb
69
+ - lib/puppet/util/puppetdb/global_check.rb
70
+ - lib/puppetdb-terminus.rb
71
+ - lib/puppetdb/terminus.rb
72
+ - lib/puppetdb/terminus/version.rb
73
+ - lib/quixoten-puppetdb-terminus.rb
74
+ - puppetdb-terminus.gemspec
75
+ homepage: https://github.com/quixoten/puppetdb-terminus
76
+ licenses:
77
+ - Apache 2.0
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">"
91
+ - !ruby/object:Gem::Version
92
+ version: 1.3.1
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.2.2
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: PuppetDB Terminus
99
+ test_files: []