puppetdb_query 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|