puppetdb_query 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,90 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ require_relative 'logging'
5
+
6
+ module PuppetDBQuery
7
+ # access puppetdb data
8
+ class PuppetDB
9
+ include Logging
10
+
11
+ NODES = "/v4/nodes".freeze
12
+ FACTS = "/v4/facts".freeze
13
+
14
+ def initialize(host = HOST, port = 443, protocol = "https", nodes = NODES, facts = FACTS)
15
+ @nodes_url = "#{protocol}://#{host}:#{port}#{nodes}"
16
+ @facts_url = "#{protocol}://#{host}:#{port}#{facts}"
17
+ @lock = Mutex.new
18
+ end
19
+
20
+ # get array of node names
21
+ def nodes
22
+ # TODO: perhaps we have to ignore entries without "deactivated": null?
23
+ # TODO: in '/v3/nodes' we must take 'name'
24
+ api_nodes.map { |data| data['certname'] }
25
+ end
26
+
27
+ # get array of node names
28
+ def nodes_properties
29
+ result = {}
30
+ api_nodes.each do |data|
31
+ next if data['deactivated']
32
+ # TODO: in '/v3/nodes' we must take 'name'
33
+ name = data['certname']
34
+ values = data.dup
35
+ %w(deactivated certname).each { |key| values.delete(key) }
36
+ result[name] = values
37
+ end
38
+ result
39
+ end
40
+
41
+ # get all nodes that have updated facts
42
+ def nodes_update_facts_since(timestamp)
43
+ ts = (timestamp.is_a?(String) ? Time.iso8601(ts) : timestamp)
44
+ nodes_properties.delete_if do |_k, data|
45
+ # TODO: in '/v3/nodes' we must take 'facts_timestamp'
46
+ !data["facts-timestamp"] || Time.iso8601(data["facts-timestamp"]) < ts
47
+ end.keys
48
+ end
49
+
50
+ # get hash of facts for given node name
51
+ def node_facts(node)
52
+ json = get_json("#{@nodes_url}/#{node}/facts", 10)
53
+ return nil if json.include?("error")
54
+ Hash[json.map { |data| [data["name"], data["value"]] }]
55
+ end
56
+
57
+ # get all nodes with all facts
58
+ def facts
59
+ json = get_json(@facts_url, 60)
60
+ result = {}
61
+ json.each do |fact|
62
+ data = result[fact["certname"]]
63
+ result[fact["certname"]] = data = {} unless data
64
+ data[fact["name"]] = fact["value"]
65
+ end
66
+ result
67
+ end
68
+
69
+ def api_nodes
70
+ get_json(@nodes_url, 10)
71
+ end
72
+
73
+ private
74
+
75
+ def get_json(url, timeout)
76
+ @lock.synchronize do
77
+ logger.info "get json from #{url}"
78
+ uri = URI.parse(url)
79
+ http = Net::HTTP.new(uri.host, uri.port)
80
+ http.use_ssl = uri.scheme == 'https'
81
+ http.read_timeout = timeout
82
+ request = Net::HTTP::Get.new(uri.request_uri)
83
+ request['Accept'] = "application/json"
84
+ response = http.request(request)
85
+ logger.info " got #{response.body.size} characters from #{url}"
86
+ JSON.parse(response.body)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,31 @@
1
+ module PuppetDBQuery
2
+ # represent a term containing an operator and arguments
3
+ class Term
4
+ attr_reader :operator
5
+ attr_reader :args
6
+
7
+ def initialize(operator)
8
+ @operator = operator
9
+ @args = []
10
+ end
11
+
12
+ def add(*arg)
13
+ @args += arg
14
+ self
15
+ end
16
+
17
+ def ==(other)
18
+ other.class == self.class && other.operator == operator && other.args == args
19
+ end
20
+
21
+ def to_s
22
+ if operator.prefix?
23
+ "#{operator}(#{args.join(', ')})"
24
+ elsif operator.infix?
25
+ "(#{args.join(" #{operator} ")})"
26
+ else
27
+ raise "unkown representation for operator: #{operator}"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,52 @@
1
+ require_relative "parser"
2
+ require_relative "logging"
3
+
4
+ module PuppetDBQuery
5
+ # convert puppetdb query into mongodb query
6
+ class ToMongo
7
+ include Logging
8
+
9
+ def query(string)
10
+ logger.info "transfer following string into mongo query:"
11
+ logger.info(string)
12
+ terms = Parser.parse(string)
13
+ mongo_query = query_term(terms[0])
14
+ logger.info "resulting mongo query:"
15
+ logger.info mongo_query.inspect
16
+ mongo_query
17
+ end
18
+
19
+ private
20
+
21
+ # rubocop:disable Metrics/PerceivedComplexity
22
+ def query_term(term)
23
+ # rubocop:disable Style/GuardClause
24
+ if term.is_a?(Symbol)
25
+ return term.to_s
26
+ elsif term.is_a?(Integer)
27
+ return "'#{term}'"
28
+ elsif term.is_a?(TrueClass)
29
+ return term.to_s
30
+ elsif !term.is_a?(Term)
31
+ return "'#{term}'"
32
+ end
33
+ # rubocop:enable Style/GuardClause
34
+ terms = term.args.map { |t| query_term(t) }
35
+ case term.operator.symbol
36
+ when :and
37
+ { :$and => terms }
38
+ when :or
39
+ { :$or => terms }
40
+ when :equal
41
+ { term.args[0] => term.args[1].to_s }
42
+ when :not_equal
43
+ { term.args[0] => { :$ne => term.args[1].to_s } }
44
+ when :match
45
+ { term.args[0] => { :$regex => term.args[1].to_s } }
46
+ else
47
+ raise "can't handle operator '#{term.operator}' yet"
48
+ end
49
+ end
50
+ # rubocop:enable Metrics/PerceivedComplexity
51
+ end
52
+ end
@@ -0,0 +1,185 @@
1
+ require_relative "logging"
2
+
3
+ module PuppetDBQuery
4
+ # tokenize puppetdb queries
5
+ # rubocop:disable Metrics/ClassLength
6
+ class Tokenizer
7
+ include Logging
8
+ include Enumerable
9
+
10
+ SINGLE_CHAR_TO_TOKEN = {
11
+ "!" => :not,
12
+ "=" => :equal,
13
+ "(" => :begin,
14
+ ")" => :end,
15
+ "[" => :list_begin,
16
+ "]" => :list_end,
17
+ "<" => :less,
18
+ ">" => :greater,
19
+ "~" => :match,
20
+ "," => :comma,
21
+ }.freeze
22
+
23
+ DOUBLE_CHAR_TO_TOKEN = {
24
+ "!=" => :not_equal,
25
+ "!~" => :not_match,
26
+ "~>" => :match_array,
27
+ "<=" => :less_or_equal,
28
+ ">=" => :greater_or_equal,
29
+ }.freeze
30
+
31
+ STRING_TO_TOKEN = {
32
+ "not" => :not,
33
+ "or" => :or,
34
+ "and" => :and,
35
+ "in" => :in,
36
+ "is" => :is,
37
+ "null" => :null,
38
+ "true" => :true,
39
+ "false" => :false,
40
+ }.freeze
41
+
42
+ LANGUAGE_TOKENS = SINGLE_CHAR_TO_TOKEN.merge(DOUBLE_CHAR_TO_TOKEN).merge(STRING_TO_TOKEN).freeze
43
+ LANGUAGE_STRINGS = LANGUAGE_TOKENS.invert.freeze
44
+
45
+ def self.symbols(query)
46
+ r = []
47
+ tokenizer = Tokenizer.new(query)
48
+ r << tokenizer.next_token until tokenizer.empty?
49
+ r
50
+ end
51
+
52
+ def self.query(symbols)
53
+ symbols.map { |v| symbol_to_string(v) }.join(" ")
54
+ end
55
+
56
+ def self.symbol_to_string(s)
57
+ (LANGUAGE_STRINGS[s] || (s.is_a?(Symbol) ? s.to_s : nil) || s.inspect).to_s
58
+ end
59
+
60
+ def self.idem(query)
61
+ query(symbols(query))
62
+ end
63
+
64
+ attr_reader :position
65
+ attr_reader :text
66
+
67
+ def initialize(text)
68
+ @text = text
69
+ @position = 0
70
+ end
71
+
72
+ def next_token
73
+ skip_whitespace
74
+ return nil if empty?
75
+ read_token
76
+ end
77
+
78
+ def empty?
79
+ position >= text.size
80
+ end
81
+
82
+ def each
83
+ yield next_token until empty?
84
+ end
85
+
86
+ private
87
+
88
+ def read_token
89
+ logger.debug "read token"
90
+ skip_whitespace
91
+ return nil if empty?
92
+ s = text[position, 2]
93
+ if DOUBLE_CHAR_TO_TOKEN.include?(s)
94
+ increase
95
+ increase
96
+ return DOUBLE_CHAR_TO_TOKEN[s]
97
+ end
98
+ c = text[position]
99
+ if SINGLE_CHAR_TO_TOKEN.include?(c)
100
+ increase
101
+ return SINGLE_CHAR_TO_TOKEN[c]
102
+ end
103
+ case c
104
+ when /[a-zA-Z]/
105
+ return read_symbol
106
+ when "'", '"'
107
+ return read_quoted
108
+ when /[-0-9]/
109
+ return read_number
110
+ else
111
+ error("unknown kind of token: '#{c}'")
112
+ end
113
+ end
114
+
115
+ def read_quoted
116
+ logger.debug "read quoted"
117
+ skip_whitespace
118
+ q = text[position]
119
+ increase
120
+ r = ""
121
+ while !empty? && (c = text[position]) != q
122
+ if c == "\\"
123
+ increase
124
+ c = text[position] unless empty?
125
+ case c
126
+ when 'r'
127
+ c = "\r"
128
+ when 'n'
129
+ c = "\n"
130
+ when '\''
131
+ c = "\\"
132
+ end
133
+ end
134
+ r << c
135
+ increase
136
+ end
137
+ error("I expected '#{q}' but I got '#{c}'") if c != q
138
+ increase
139
+ logger.debug "resulting string: '#{r}'"
140
+ r
141
+ end
142
+
143
+ def read_symbol
144
+ logger.debug "read symbol"
145
+ skip_whitespace
146
+ r = ""
147
+ while !empty? && (c = text[position]) =~ /[-a-zA-Z_0-9]/
148
+ r << c
149
+ increase
150
+ end
151
+ logger.debug "resulting symbol: '#{r}'"
152
+ r.to_sym
153
+ end
154
+
155
+ def read_number
156
+ logger.debug "read number"
157
+ skip_whitespace
158
+ r = ""
159
+ while !empty? && (c = text[position]) =~ /[-0-9\.E]/
160
+ r << c
161
+ increase
162
+ end
163
+ logger.debug "resulting number: '#{r}'"
164
+ Integer(r)
165
+ rescue
166
+ Float(r)
167
+ end
168
+
169
+ def skip_whitespace
170
+ # logger.debug "skip whitespace"
171
+ return if empty?
172
+ increase until empty? || text[position] !~ /\s/
173
+ end
174
+
175
+ def increase
176
+ # logger.debug "increase"
177
+ @position += 1
178
+ # logger.debug position
179
+ end
180
+
181
+ def error(message)
182
+ raise "tokenizing query failed\n#{message}\n\n#{text}\n#{' ' * position}^"
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,76 @@
1
+ require_relative "logging"
2
+
3
+ module PuppetDBQuery
4
+ # update nodes data from source to destination
5
+ class Updater
6
+ include Logging
7
+
8
+ attr_reader :source
9
+ attr_reader :destination
10
+
11
+ def initialize(source, destination)
12
+ @source = source
13
+ @destination = destination
14
+ end
15
+
16
+ # update by deleting missing nodes and iterating over all nodes and
17
+ # update or insert facts for each one
18
+ #
19
+ # 335.6 seconds: update time for 1561 nodes
20
+ def update
21
+ source_nodes = source.nodes
22
+ destination_nodes = destination.nodes
23
+ (destination_nodes - source_nodes).each do |node|
24
+ destination.node_delete(node)
25
+ end
26
+ source_nodes.each do |node|
27
+ begin
28
+ destination.node_update(node, source.node_facts(node))
29
+ rescue
30
+ logging.error $!
31
+ end
32
+ end
33
+ end
34
+
35
+ # update by deleting missing nodes and get a complete map of nodes with facts
36
+ # and update or insert facts for each one
37
+ #
38
+ # 166.4 seconds: update time for 1561 nodes
39
+ def update2
40
+ source_nodes = source.nodes
41
+ destination_nodes = destination.nodes
42
+ (destination_nodes - source_nodes).each do |node|
43
+ destination.node_delete(node)
44
+ end
45
+ complete = source.facts
46
+ complete.each do |node, facts|
47
+ begin
48
+ destination.node_update(node, facts)
49
+ rescue
50
+ logging.error $!
51
+ end
52
+ end
53
+ end
54
+
55
+ # update by deleting missing nodes and getting a list of nodes
56
+ # with changed facts, iterate over them and update or insert facts for each one
57
+ #
58
+ # update time depends extremly on the number of changed nodes
59
+ def update3(last_update_timestamp)
60
+ source_nodes = source.nodes
61
+ destination_nodes = destination.nodes
62
+ (destination_nodes - source_nodes).each do |node|
63
+ destination.node_delete(node)
64
+ end
65
+ modified = source.nodes_update_facts_since(last_update_timestamp)
66
+ modified.each do |node|
67
+ begin
68
+ destination.node_update(node, source.node_facts(node))
69
+ rescue
70
+ logging.error $!
71
+ end
72
+ end
73
+ modified.size
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module PuppetDBQuery
2
+ VERSION = "0.0.3".freeze
3
+ end
@@ -0,0 +1,14 @@
1
+ require_relative "puppetdb_query/logging"
2
+ require_relative "puppetdb_query/operator"
3
+ require_relative "puppetdb_query/term"
4
+ require_relative "puppetdb_query/tokenizer"
5
+ require_relative "puppetdb_query/parser"
6
+ require_relative "puppetdb_query/puppetdb"
7
+ require_relative "puppetdb_query/mongodb"
8
+ require_relative "puppetdb_query/to_mongo"
9
+ require_relative "puppetdb_query/updater"
10
+ require_relative "puppetdb_query/version"
11
+
12
+ # use puppetdb query language to ask a mongodb for node facts
13
+ module PuppetDBQuery
14
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'puppetdb_query/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "puppetdb_query"
8
+ spec.version = PuppetDBQuery::VERSION
9
+ spec.authors = ["Michael Meyling"]
10
+ spec.email = ["search@meyling.com"]
11
+ spec.summary = %q{access puppetdb data from other sources}
12
+ spec.description = %q{Allow puppetdb access from resources like mongodb.}
13
+ spec.homepage = "https://github.com/m-31/puppetdb_query"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+ spec.post_install_message = " Sitting quietly, doing nothing, spring comes, and grass grows by itself."
22
+
23
+ spec.add_dependency "mongo"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "rubocop" #, "0.43.0" # matches with .rubocop.yml
27
+ spec.add_development_dependency "easy_diff"
28
+ end
@@ -0,0 +1,40 @@
1
+ require "spec_helper"
2
+
3
+ # rubocop:disable Style/SpaceInsideBrackets,Style/MultilineArrayBraceLayout
4
+ # rubocop:disable Style/MultilineMethodCallIndentation,Style/RedundantParentheses
5
+ # rubocop:disable Style/ClosingParenthesisIndentation
6
+ describe PuppetDBQuery::Parser do
7
+ PARSER_DATA = [
8
+ [ 'hostname=\'puppetdb-mike-217922\'',
9
+ [PuppetDBQuery::Term.new(PuppetDBQuery::Parser::EQUAL).add(:hostname, "puppetdb-mike-217922")]
10
+ ],
11
+ [ 'disable_puppet = true',
12
+ [PuppetDBQuery::Term.new(PuppetDBQuery::Parser::EQUAL).add(:disable_puppet, :true)]
13
+ ],
14
+ [ 'fqdn~"app-dev" and group=develop and vertical~tracking and cluster_color~BLUE',
15
+ [PuppetDBQuery::Term.new(PuppetDBQuery::Parser::AND)
16
+ .add(PuppetDBQuery::Term.new(PuppetDBQuery::Parser::MATCH).add(:fqdn, "app-dev"))
17
+ .add(PuppetDBQuery::Term.new(PuppetDBQuery::Parser::EQUAL).add(:group, :develop))
18
+ .add(PuppetDBQuery::Term.new(PuppetDBQuery::Parser::MATCH).add(:vertical, :tracking))
19
+ .add(PuppetDBQuery::Term.new(PuppetDBQuery::Parser::MATCH).add(:cluster_color, :BLUE))
20
+ ]
21
+ ],
22
+ [ 'a~"A" and g=G or v~t and c~B',
23
+ [PuppetDBQuery::Term.new(PuppetDBQuery::Parser::OR)
24
+ .add((PuppetDBQuery::Term.new(PuppetDBQuery::Parser::AND)
25
+ .add(PuppetDBQuery::Term.new(PuppetDBQuery::Parser::MATCH).add(:a, "A"))
26
+ .add(PuppetDBQuery::Term.new(PuppetDBQuery::Parser::EQUAL).add(:g, :G))
27
+ )).add((PuppetDBQuery::Term.new(PuppetDBQuery::Parser::AND)
28
+ .add(PuppetDBQuery::Term.new(PuppetDBQuery::Parser::MATCH).add(:v, :t))
29
+ .add(PuppetDBQuery::Term.new(PuppetDBQuery::Parser::MATCH).add(:c, :B))
30
+ ))
31
+ ]
32
+ ],
33
+ ].freeze
34
+
35
+ PARSER_DATA.each do |q, a|
36
+ it "translates correctly #{q.inspect}" do
37
+ expect(subject.parse(q)).to eq(a)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,68 @@
1
+ require "spec_helper"
2
+
3
+ # rubocop:disable Style/SpaceInsideBrackets,Style/MultilineArrayBraceLayout,Style/IndentArray
4
+ # rubocop:disable Style/MultilineHashBraceLayout
5
+ describe PuppetDBQuery::ToMongo do
6
+ TO_MONGO_DATA = [
7
+ [ "hostname='puppetdb-mike-217922'",
8
+ { hostname: "puppetdb-mike-217922" }
9
+ ],
10
+ [ 'disable_puppet = true',
11
+ { disable_puppet: "true" }
12
+ ],
13
+ [ 'fqdn~"app-dev" and group=develop and vertical~tracking and cluster_color~BLUE',
14
+ { :$and => [
15
+ { fqdn: { :$regex => "app-dev" } },
16
+ { group: "develop" },
17
+ { vertical: { :$regex => "tracking" } },
18
+ { cluster_color: { :$regex => "BLUE" } }
19
+ ]
20
+ }
21
+ ],
22
+ [ 'fqdn~"kafka" and group=develop and vertical=tracking',
23
+ { :$and => [
24
+ { fqdn: { :$regex => "kafka" } },
25
+ { group: "develop" },
26
+ { vertical: "tracking" }
27
+ ]
28
+ }
29
+ ],
30
+ [ '(group="develop-ci" or group=develop or group=mock) and (operatingsystemmajrelease="6")',
31
+ { :$and => [
32
+ { :$or => [
33
+ { group: "develop-ci" },
34
+ { group: "develop" },
35
+ { group: "mock" }
36
+ ]
37
+ },
38
+ { operatingsystemmajrelease: "6" }
39
+ ]
40
+ }
41
+ ],
42
+ [ "server_type=zoo or server_type='mesos-magr') and group!='infrastructure-ci'",
43
+ { :$or => [
44
+ { server_type: "zoo" },
45
+ { server_type: "mesos-magr" }
46
+ ]
47
+ }
48
+ ],
49
+ [ "server_type~'mesos-magr' and group='ops-ci' and operatingsystemmajrelease=7 and" \
50
+ " vmtest_vm!=true and disable_puppet!=true and puppet_artifact_version!=NO_VERSION_CHECK",
51
+ { :$and => [
52
+ { server_type: { :$regex => "mesos-magr" } },
53
+ { group: "ops-ci" },
54
+ { operatingsystemmajrelease: "7" },
55
+ { vmtest_vm: { :$ne => "true" } },
56
+ { disable_puppet: { :$ne => "true" } },
57
+ { puppet_artifact_version: { :$ne => "NO_VERSION_CHECK" } }
58
+ ]
59
+ }
60
+ ],
61
+ ].freeze
62
+
63
+ TO_MONGO_DATA.each do |q, a|
64
+ it "translates correctly #{q.inspect}" do
65
+ expect(subject.query(q)).to eq(a)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ # rubocop:disable Style/SpaceInsideBrackets,Style/MultilineArrayBraceLayout
4
+ describe PuppetDBQuery::Tokenizer do
5
+ TOKENIZER_DATA = [
6
+ [ 'hostname=\'puppetdb-mike-217922\'',
7
+ [:hostname, :equal, "puppetdb-mike-217922"]
8
+ ],
9
+ [ 'disable_puppet = true',
10
+ [:disable_puppet, :equal, :true]
11
+ ],
12
+ [ 'fqdn~"app-dev" and group=develop and vertical~tracking and cluster_color~BLUE',
13
+ [:fqdn, :match, "app-dev", :and, :group, :equal, :develop, :and, :vertical, :match,
14
+ :tracking, :and, :cluster_color, :match, :BLUE]
15
+ ],
16
+ [ 'fqdn~"kafka" and group=develop and vertical=tracking',
17
+ [:fqdn, :match, "kafka", :and, :group, :equal, :develop, :and, :vertical, :equal, :tracking]
18
+ ],
19
+ [ '(group="develop-ci" or group=develop or group=mock) and (operatingsystemmajrelease="6")',
20
+ [:begin, :group, :equal, "develop-ci", :or, :group, :equal, :develop, :or, :group, :equal,
21
+ :mock, :end, :and, :begin, :operatingsystemmajrelease, :equal, "6", :end]
22
+ ],
23
+ [ "server_type=zoo or server_type='mesos-magr') and group!='infrastructure-ci'",
24
+ [:server_type, :equal, :zoo, :or, :server_type, :equal, "mesos-magr", :end, :and, :group,
25
+ :not_equal, "infrastructure-ci"]
26
+ ],
27
+ [ "server_type~'mesos-magr' and group='ops-ci' and operatingsystemmajrelease=7 and" \
28
+ " vmtest_vm!=true and disable_puppet!=true and puppet_artifact_version!=NO_VERSION_CHECK",
29
+ [:server_type, :match, "mesos-magr", :and, :group, :equal, "ops-ci", :and,
30
+ :operatingsystemmajrelease, :equal, 7, :and, :vmtest_vm, :not_equal, :true, :and,
31
+ :disable_puppet, :not_equal, :true, :and, :puppet_artifact_version, :not_equal,
32
+ :NO_VERSION_CHECK]
33
+ ],
34
+ ].freeze
35
+
36
+ TOKENIZER_DATA.each do |q, a|
37
+ describe "translates correctly #{q.inspect}" do
38
+ subject { PuppetDBQuery::Tokenizer.new(q) }
39
+ it "into tokens" do
40
+ expect(subject.map { |n| n }).to eq(a)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,7 @@
1
+ require "spec_helper"
2
+
3
+ describe PuppetDBQuery do
4
+ it "has a version number" do
5
+ expect(PuppetDBQuery::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'puppetdb_query'
3
+
4
+ # no logging output during spec tests
5
+ include PuppetDBQuery::Logging
6
+ logger.level = Logger::FATAL
7
+
8
+ # we want to be able to test protected or private methods
9
+ RSpec.configure do |config|
10
+ config.before(:each) do
11
+ described_class.send(:public, *described_class.protected_instance_methods)
12
+ described_class.send(:public, *described_class.private_instance_methods)
13
+ end
14
+ end