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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +102 -0
- data/.travis.yml +19 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +64 -0
- data/Rakefile +19 -0
- data/lib/puppetdb_query/logging.rb +45 -0
- data/lib/puppetdb_query/mongodb.rb +134 -0
- data/lib/puppetdb_query/operator.rb +38 -0
- data/lib/puppetdb_query/parser.rb +186 -0
- data/lib/puppetdb_query/puppetdb.rb +90 -0
- data/lib/puppetdb_query/term.rb +31 -0
- data/lib/puppetdb_query/to_mongo.rb +52 -0
- data/lib/puppetdb_query/tokenizer.rb +185 -0
- data/lib/puppetdb_query/updater.rb +76 -0
- data/lib/puppetdb_query/version.rb +3 -0
- data/lib/puppetdb_query.rb +14 -0
- data/puppetdb_query.gemspec +28 -0
- data/spec/puppetdb_query/parser_spec.rb +40 -0
- data/spec/puppetdb_query/to_mongo_spec.rb +68 -0
- data/spec/puppetdb_query/tokenizer_spec.rb +44 -0
- data/spec/puppetdb_query_spec.rb +7 -0
- data/spec/spec_helper.rb +14 -0
- metadata +144 -0
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|