puppetdb_query 0.0.3

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,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