quixoten-puppetdb-terminus 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []