ruby-puppetdb 1.6.1 → 2.0.0

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.
Files changed (34) hide show
  1. checksums.yaml +8 -8
  2. data/bin/find-nodes +34 -21
  3. data/lib/hiera/backend/puppetdb_backend.rb +15 -12
  4. data/lib/puppet/application/query.rb +10 -4
  5. data/lib/puppet/face/query.rb +37 -31
  6. data/lib/puppet/parser/functions/query_facts.rb +9 -4
  7. data/lib/puppet/parser/functions/query_nodes.rb +13 -8
  8. data/lib/puppet/parser/functions/query_resources.rb +35 -6
  9. data/lib/puppetdb.rb +1 -1
  10. data/lib/puppetdb/astnode.rb +92 -44
  11. data/lib/puppetdb/connection.rb +14 -69
  12. data/lib/puppetdb/grammar.racc +75 -40
  13. data/lib/puppetdb/lexer.rb +23 -4
  14. data/lib/puppetdb/lexer.rex +30 -24
  15. data/lib/puppetdb/parser.rb +245 -223
  16. data/lib/puppetdb/parser_helper.rb +48 -0
  17. data/spec/unit/puppetdb/parser_spec.rb +129 -0
  18. metadata +7 -28
  19. data/lib/puppet/parser/functions/pdbfactquery.rb +0 -31
  20. data/lib/puppet/parser/functions/pdbnodequery.rb +0 -38
  21. data/lib/puppet/parser/functions/pdbnodequery_all.rb +0 -40
  22. data/lib/puppet/parser/functions/pdbquery.rb +0 -41
  23. data/lib/puppet/parser/functions/pdbresourcequery.rb +0 -39
  24. data/lib/puppet/parser/functions/pdbresourcequery_all.rb +0 -37
  25. data/lib/puppet/parser/functions/pdbstatusquery.rb +0 -31
  26. data/lib/puppetdb/util.rb +0 -18
  27. data/spec/unit/puppet/parser/functions/pdbfactquery_spec.rb +0 -19
  28. data/spec/unit/puppet/parser/functions/pdbnodequery_all_spec.rb +0 -19
  29. data/spec/unit/puppet/parser/functions/pdbnodequery_spec.rb +0 -19
  30. data/spec/unit/puppet/parser/functions/pdbquery_spec.rb +0 -19
  31. data/spec/unit/puppet/parser/functions/pdbresourcequery_all_spec.rb +0 -19
  32. data/spec/unit/puppet/parser/functions/pdbresourcequery_spec.rb +0 -19
  33. data/spec/unit/puppet/parser/functions/pdbstatusquery_spec.rb +0 -19
  34. data/spec/unit/puppetdb/connection_spec.rb +0 -67
data/lib/puppetdb.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module PuppetDB
2
2
  # Current version of this module
3
- VERSION = [1,6,1]
3
+ VERSION = [2, 0, 0]
4
4
  end
@@ -1,16 +1,14 @@
1
1
  class PuppetDB::ASTNode
2
2
  attr_accessor :type, :value, :children
3
3
 
4
- def initialize(type, value, children=[])
4
+ def initialize(type, value, children = [])
5
5
  @type = type
6
6
  @value = value
7
7
  @children = children
8
8
  end
9
9
 
10
- def capitalize!
11
- @value=@value.to_s.split("::").collect { |s| s.capitalize }.join("::")
12
- @children.each { |c| c.capitalize! }
13
- return self
10
+ def capitalize_class(name)
11
+ name.to_s.split('::').collect(&:capitalize).join('::')
14
12
  end
15
13
 
16
14
  # Generate the the query code for a subquery
@@ -26,9 +24,9 @@ class PuppetDB::ASTNode
26
24
  if from_mode == :none
27
25
  return query
28
26
  else
29
- return ['in', (from_mode == :nodes) ? 'name' : 'certname',
30
- ['extract', (to_mode == :nodes) ? 'name' : 'certname',
31
- ["select-#{to_mode.to_s}", query]]]
27
+ return ['in', 'certname',
28
+ ['extract', 'certname',
29
+ ["select_#{to_mode}", query]]]
32
30
  end
33
31
  end
34
32
 
@@ -40,58 +38,108 @@ class PuppetDB::ASTNode
40
38
  case @type
41
39
  when :booleanop
42
40
  @children.each do |c|
43
- if c.type == :booleanop and c.value == @value
41
+ if c.type == :booleanop && c.value == @value
44
42
  c.children.each { |cc| @children << cc }
45
43
  @children.delete c
46
44
  end
47
45
  end
48
46
  end
49
- @children.each { |c| c.optimize }
50
- return self
47
+ @children.each(&:optimize)
48
+ self
51
49
  end
52
50
 
53
51
  # Evalutate the node and all children
54
52
  #
55
53
  # @param mode [Symbol] The query mode we are evaluating for
56
54
  # @return [Array] the resulting PuppetDB query
57
- def evaluate(mode = :nodes)
55
+ def evaluate(mode = [:nodes])
58
56
  case @type
59
- when :booleanop
60
- return [@value.to_s, *evaluate_children(mode)]
61
- when :subquery
62
- return subquery(mode, @value, *evaluate_children(@value))
63
- when :exp
64
- case @value
65
- when :equals then op = '='
66
- when :greaterthan then op = '>'
67
- when :lessthan then op = '<'
68
- when :match then op = '~'
69
- end
70
-
71
- case mode
72
- when :nodes,:facts # Do a subquery to match nodes matching the facts
73
- return subquery(mode, :facts, ['and', ['=', 'name', @children[0].evaluate(mode)], [op, 'value', @children[1].evaluate(mode)]])
74
- when :resources
75
- paramname = @children[0].evaluate(mode)
76
- case paramname
77
- when "tag"
78
- return [op, paramname, @children[1].evaluate(mode)]
57
+ when :comparison
58
+ left = @children[0].evaluate(mode)
59
+ right = @children[1].evaluate(mode)
60
+ if mode.last == :subquery
61
+ left = left[0] if left.length == 1
62
+ comparison(left, right)
63
+ elsif mode.last == :resources
64
+ if left[0] == 'tag'
65
+ comparison(left[0], right)
79
66
  else
80
- return [op, ['parameter', paramname], @children[1].evaluate(mode)]
67
+ comparison(['parameter', left[0]], right)
81
68
  end
69
+ else
70
+ subquery(mode.last,
71
+ :fact_contents,
72
+ ['and', left, comparison('value', right)])
82
73
  end
74
+ when :boolean
75
+ value
83
76
  when :string
84
- return @value.to_s
77
+ value
85
78
  when :number
86
- return @value
87
- when :boolean
88
- return @value
89
- when :resourcetitle
90
- return [@value, 'title', @children[0].evaluate(mode)]
91
- when :resourcetype
92
- return ['=', 'type', @value]
93
- when :resexported
94
- return ['=', 'exported', @value]
79
+ value
80
+ when :date
81
+ require 'chronic'
82
+ ret = Chronic.parse(value, :guess => false).first.iso8601
83
+ fail "Failed to parse datetime: #{value}" if ret.nil?
84
+ ret
85
+ when :booleanop
86
+ [value.to_s, *evaluate_children(mode)]
87
+ when :subquery
88
+ mode.push :subquery
89
+ ret = subquery(mode.last, value + 's', children[0].evaluate(mode))
90
+ mode.pop
91
+ ret
92
+ when :regexp_node_match
93
+ mode.push :regexp
94
+ ret = ['~', 'certname', Regexp.escape(value.evaluate(mode))]
95
+ mode.pop
96
+ ret
97
+ when :identifier_path
98
+ if mode.last == :subquery || mode.last == :resources
99
+ evaluate_children(mode)
100
+ elsif mode.last == :regexp
101
+ evaluate_children(mode).join '.'
102
+ else
103
+ # Check if any of the children are of regexp type
104
+ # in that case we need to escape the others and use the ~> operator
105
+ if children.any? { |c| c.type == :regexp_identifier }
106
+ mode.push :regexp
107
+ ret = ['~>', 'path', evaluate_children(mode)]
108
+ mode.pop
109
+ ret
110
+ else
111
+ ['=', 'path', evaluate_children(mode)]
112
+ end
113
+ end
114
+ when :regexp_identifier
115
+ value
116
+ when :identifier
117
+ mode.last == :regexp ? Regexp.escape(value) : value
118
+ when :resource
119
+ mode.push :resources
120
+ regexp = value[:title].type == :regexp_identifier
121
+ if !regexp && value[:type].capitalize == 'Class'
122
+ title = capitalize_class(value[:title].evaluate)
123
+ else
124
+ title = value[:title].evaluate
125
+ end
126
+ ret = subquery(mode.last, :resources,
127
+ ['and',
128
+ ['=', 'type', capitalize_class(value[:type])],
129
+ [regexp ? '~' : '=', 'title', title],
130
+ ['=', 'exported', value[:exported]],
131
+ *evaluate_children(mode)])
132
+ mode.pop
133
+ ret
134
+ end
135
+ end
136
+
137
+ # Helper method to produce a comparison expression
138
+ def comparison(left, right)
139
+ if @value[0] == '!'
140
+ ['not', [@value[1], left, right]]
141
+ else
142
+ [@value, left, right]
95
143
  end
96
144
  end
97
145
 
@@ -99,6 +147,6 @@ class PuppetDB::ASTNode
99
147
  #
100
148
  # @return [Array] The evaluate results of the children nodes
101
149
  def evaluate_children(mode)
102
- return children.collect { |c| c.evaluate mode }
150
+ children.collect { |c| c.evaluate mode }
103
151
  end
104
152
  end
@@ -9,79 +9,24 @@ class PuppetDB::Connection
9
9
 
10
10
  include Puppet::Util::Logging
11
11
 
12
- def initialize(host='puppetdb', port=443, use_ssl=true)
12
+ def initialize(host = 'puppetdb', port = 443, use_ssl = true)
13
13
  @host = host
14
14
  @port = port
15
15
  @use_ssl = use_ssl
16
- @parser = PuppetDB::Parser.new
17
16
  end
18
17
 
19
- # Parse a query string into a PuppetDB query
20
- #
21
- # @param query [String] the query string to parse
22
- # @param endpoint [Symbol] the endpoint for which the query should be evaluated
23
- # @return [Array] the PuppetDB query
24
- def parse_query(query, endpoint = :nodes)
25
- if query = @parser.scan_str(query)
26
- query.optimize.evaluate endpoint
27
- end
28
- end
29
-
30
- # Get the listed facts for all nodes matching query
31
- # return it as a hash of hashes
32
- #
33
- # @param facts [Array] the list of facts to fetch
34
- # @param nodequery [Array] the query to find the nodes to fetch facts for
35
- # @return [Hash] a hash of hashes with facts for each node mathing query
36
- def facts(facts, nodequery, http = nil)
37
- if facts.empty?
38
- q = ['in', 'certname', ['extract', 'certname', ['select-facts', nodequery]]]
39
- else
40
- q = ['and', ['in', 'certname', ['extract', 'certname', ['select-facts', nodequery]]], ['or', *facts.collect { |f| ['=', 'name', f]}]]
41
- end
42
- facts = {}
43
- query(:facts, q, http).each do |fact|
44
- if facts.include? fact['certname']
45
- facts[fact['certname']][fact['name']] = fact['value']
46
- else
47
- facts[fact['certname']] = { fact['name'] => fact['value'] }
18
+ def self.check_version
19
+ begin
20
+ require 'puppet/util/puppetdb'
21
+ unless Puppet::Util::Puppetdb.config.respond_to?('server_urls')
22
+ Puppet.warning <<-EOT
23
+ It looks like you are using a PuppetDB version < 3.0.
24
+ This version of puppetdbquery requires at least PuppetDB 3.0 to work.
25
+ Downgrade to puppetdbquery 1.x to use it with PuppetDB 2.x.
26
+ EOT
48
27
  end
28
+ rescue LoadError
49
29
  end
50
- facts
51
- end
52
-
53
- # Get the listed resources for all nodes matching query
54
- # return it as a hash of hashes
55
- #
56
- # @param resquery [Array] a resources query for what resources to fetch
57
- # @param nodequery [Array] the query to find the nodes to fetch resources for, optionally empty
58
- # @param grouphosts [Boolean] whether or not to group the results by the host they belong to
59
- # @return [Hash|Array] a hash of hashes with resources for each node mathing query or array if grouphosts was false
60
- def resources(nodequery, resquery, http=nil, grouphosts=true)
61
- if resquery and ! resquery.empty?
62
- if nodequery and ! nodequery.empty?
63
- q = ['and', resquery, nodequery]
64
- else
65
- q = resquery
66
- end
67
- else
68
- raise RuntimeError, "PuppetDB resources query error: at least one argument must be non empty; arguments were: nodequery: #{nodequery.inspect} and requery: #{resquery.inspect}"
69
- end
70
- resources = {}
71
- results = query(:resources, q, http)
72
-
73
- if grouphosts
74
- results.each do |resource|
75
- unless resources.has_key? resource['certname']
76
- resources[resource['certname']] = []
77
- end
78
- resources[resource['certname']] << resource
79
- end
80
- else
81
- resources = results
82
- end
83
-
84
- return resources
85
30
  end
86
31
 
87
32
  # Execute a PuppetDB query
@@ -89,7 +34,7 @@ class PuppetDB::Connection
89
34
  # @param endpoint [Symbol] :resources, :facts or :nodes
90
35
  # @param query [Array] query to execute
91
36
  # @return [Array] the results of the query
92
- def query(endpoint, query = nil, http = nil, version = :v3)
37
+ def query(endpoint, query = nil, http = nil, version = :v4)
93
38
  require 'json'
94
39
 
95
40
  unless http
@@ -98,13 +43,13 @@ class PuppetDB::Connection
98
43
  end
99
44
  headers = { 'Accept' => 'application/json' }
100
45
 
101
- uri = "/#{version}/#{endpoint}"
46
+ uri = "/pdb/query/#{version}/#{endpoint}"
102
47
  uri += URI.escape "?query=#{query.to_json}" unless query.nil? || query.empty?
103
48
 
104
49
  debug("PuppetDB query: #{query.to_json}")
105
50
 
106
51
  resp = http.get(uri, headers)
107
- raise "PuppetDB query error: [#{resp.code}] #{resp.msg}, query: #{query.to_json}" unless resp.kind_of?(Net::HTTPSuccess)
52
+ fail "PuppetDB query error: [#{resp.code}] #{resp.msg}, query: #{query.to_json}" unless resp.is_a?(Net::HTTPSuccess)
108
53
  JSON.parse(resp.body)
109
54
  end
110
55
  end
@@ -4,7 +4,9 @@
4
4
  class PuppetDB::Parser
5
5
 
6
6
  token LPAREN RPAREN LBRACK RBRACK LBRACE RBRACE
7
- token EQUALS NOTEQUALS MATCH LESSTHAN GREATERTHAN
7
+ token EQUALS NOTEQUALS MATCH NOTMATCH
8
+ token LESSTHAN LESSTHANEQ GREATERTHAN GREATERTHANEQ
9
+ token AT HASH ASTERISK DOT
8
10
  token NOT AND OR
9
11
  token NUMBER STRING BOOLEAN EXPORTED
10
12
 
@@ -15,48 +17,81 @@ class PuppetDB::Parser
15
17
  left OR
16
18
  preclow
17
19
 
20
+ options no_result_var
21
+
18
22
  rule
19
- query:
20
- |exp
21
-
22
- exp: LPAREN exp RPAREN { result = val[1] }
23
- | NOT exp { result = ASTNode.new :booleanop, :not, [val[1]] }
24
- | exp AND exp { result = ASTNode.new :booleanop, :and, [val[0], val[2]] }
25
- | exp OR exp { result = ASTNode.new :booleanop, :or, [val[0], val[2]] }
26
- | string EQUALS string { result = ASTNode.new :exp, :equals, [val[0], val[2]] }
27
- | string EQUALS boolean { result = ASTNode.new :exp, :equals, [val[0], val[2]] }
28
- | string EQUALS number { result = ASTNode.new :exp, :equals, [val[0], val[2]] }
29
- | string GREATERTHAN number { result = ASTNode.new :exp, :greaterthan, [val[0], val[2]] }
30
- | string LESSTHAN number { result = ASTNode.new :exp, :lessthan, [val[0], val[2]] }
31
- | string MATCH string { result = ASTNode.new :exp, :match, [val[0], val[2]] }
32
- | string NOTEQUALS number { result = ASTNode.new :booleanop, :not, [ASTNode.new(:exp, :equals, [val[0], val[2]])] }
33
- | string NOTEQUALS boolean { result = ASTNode.new :booleanop, :not, [ASTNode.new(:exp, :equals, [val[0], val[2]])] }
34
- | string NOTEQUALS string { result = ASTNode.new :booleanop, :not, [ASTNode.new(:exp, :equals, [val[0], val[2]])] }
35
- | ressubquery
36
-
37
- ressubquery: resexp { result = ASTNode.new :subquery, :resources, [ASTNode.new(:booleanop, :and, [ASTNode.new(:resexported, false), *val[0]])] }
38
- | resexported resexp { result = ASTNode.new :subquery, :resources, [ASTNode.new(:booleanop, :and, [ASTNode.new(:resexported, true), *val[1]])] }
39
-
40
- resexp: restype { result = [val[0]] }
41
- | restitle { result = [val[0]] }
42
- | resparams { result = [val[0]] }
43
- | restype restitle { result = val[0].value == "Class" ? [val[0], val[1].capitalize!] : [val[0], val[1]] }
44
- | restitle resparams { result = [val[0], val[1]] }
45
- | restype resparams { result = [val[0], val[1]] }
46
- | restype restitle resparams { result = val[0].value == "Class" ? [val[0], val[1].capitalize!, val[2]] : [val[0], val[1], val[2]] }
47
-
48
- resexported: EXPORTED
49
- restype: STRING { result = ASTNode.new(:resourcetype, val[0]).capitalize! }
50
- restitle: LBRACK STRING RBRACK { result = ASTNode.new :resourcetitle, '=', [ASTNode.new(:string, val[1])] }
51
- restitle: LBRACK MATCH STRING RBRACK { result = ASTNode.new :resourcetitle, '~', [ASTNode.new(:string, val[2])] }
52
- resparams: LBRACE exp RBRACE { result = val[1] }
53
-
54
- string: STRING { result = ASTNode.new :string, val[0] }
55
- number: NUMBER { result = ASTNode.new :number, val[0] }
56
- boolean: BOOLEAN { result = ASTNode.new :boolean, val[0] }
23
+ query
24
+ : expression
25
+ |
26
+
27
+ expression
28
+ : identifier_path { ASTNode.new :regexp_node_match, val[0] }
29
+ | NOT expression { ASTNode.new :booleanop, :not, [val[1]] }
30
+ | expression AND expression { ASTNode.new :booleanop, :and, [val[0], val[2]] }
31
+ | expression OR expression { ASTNode.new :booleanop, :or, [val[0], val[2]] }
32
+ | LPAREN expression RPAREN { val[1] }
33
+ | resource_expression
34
+ | comparison_expression
35
+ | subquery
36
+
37
+ literal
38
+ : boolean { ASTNode.new :boolean, val[0] }
39
+ | string { ASTNode.new :string, val[0] }
40
+ | integer { ASTNode.new :number, val[0] }
41
+ | float { ASTNode.new :number, val[0] }
42
+ | AT string { ASTNode.new :date, val[1] }
43
+
44
+ comparison_op
45
+ : MATCH
46
+ | NOTMATCH
47
+ | EQUALS
48
+ | NOTEQUALS
49
+ | GREATERTHAN
50
+ | GREATERTHANEQ
51
+ | LESSTHAN
52
+ | LESSTHANEQ
53
+
54
+ comparison_expression
55
+ : identifier_path comparison_op literal { ASTNode.new :comparison, val[1], [val[0], val[2]] }
56
+
57
+ identifier
58
+ : string { ASTNode.new :identifier, val[0] }
59
+ | integer { ASTNode.new :identifier, val[0] }
60
+ | MATCH string { ASTNode.new :regexp_identifier, val[1] }
61
+ | ASTERISK { ASTNode.new :regexp_identifier, '.*' }
62
+
63
+ identifier_path
64
+ : identifier { ASTNode.new :identifier_path, nil, [val[0]] }
65
+ | identifier_path DOT identifier { val[0].children.push val[2]; val[0] }
66
+
67
+ subquery
68
+ : HASH string DOT comparison_expression { ASTNode.new :subquery, val[1], [val[3]] }
69
+ | HASH string block_expression { ASTNode.new :subquery, val[1], [val[2]] }
70
+
71
+ block_expression
72
+ : LBRACE expression RBRACE { val[1] }
73
+
74
+ resource_expression
75
+ : string LBRACK identifier RBRACK
76
+ { ASTNode.new :resource, {:type => val[0], :title => val[2], :exported => false} }
77
+ | string LBRACK identifier RBRACK block_expression
78
+ { ASTNode.new :resource, {:type => val[0], :title => val[2], :exported => false}, [val[4]] }
79
+ | EXPORTED string LBRACK identifier RBRACK
80
+ { ASTNode.new :resource, {:type => val[1], :title => val[3], :exported => true} }
81
+ | EXPORTED string LBRACK identifier RBRACK block_expression
82
+ { ASTNode.new :resource, {:type => val[1], :title => val[3], :exported => true}, [val[5]] }
83
+
84
+ boolean: BOOLEAN
85
+ integer: NUMBER
86
+ string: STRING
87
+ float: NUMBER DOT NUMBER { "#{val[0]}.#{val[2]}".to_f }
57
88
 
58
89
  end
59
- ---- header ----
90
+ ---- inner
91
+ include PuppetDB::ParserHelper
92
+
93
+ ---- header
60
94
  require 'puppetdb'
61
95
  require 'puppetdb/lexer'
62
96
  require 'puppetdb/astnode'
97
+ require 'puppetdb/parser_helper'
@@ -1,13 +1,14 @@
1
1
  #--
2
2
  # DO NOT MODIFY!!!!
3
3
  # This file is automatically generated by rex 1.0.5
4
- # from lexical definition file "lib/puppetdb/lexer.l".
4
+ # from lexical definition file "lib/puppetdb/lexer.rex".
5
5
  #++
6
6
 
7
7
  require 'racc/parser'
8
8
  # vim: syntax=ruby
9
9
 
10
10
  require 'yaml'
11
+ require 'puppetdb'
11
12
 
12
13
  class PuppetDB::Lexer < Racc::Parser
13
14
  require 'strscan'
@@ -91,12 +92,30 @@ class PuppetDB::Lexer < Racc::Parser
91
92
  when (text = @ss.scan(/~/))
92
93
  action { [:MATCH, text] }
93
94
 
95
+ when (text = @ss.scan(/\!~/))
96
+ action { [:NOTMATCH, text] }
97
+
94
98
  when (text = @ss.scan(/</))
95
99
  action { [:LESSTHAN, text] }
96
100
 
101
+ when (text = @ss.scan(/<=/))
102
+ action { [:LESSTHANEQ, text] }
103
+
97
104
  when (text = @ss.scan(/>/))
98
105
  action { [:GREATERTHAN, text] }
99
106
 
107
+ when (text = @ss.scan(/>=/))
108
+ action { [:GREATERTHANEQ, text] }
109
+
110
+ when (text = @ss.scan(/\*/))
111
+ action { [:ASTERISK, text] }
112
+
113
+ when (text = @ss.scan(/\#/))
114
+ action { [:HASH, text] }
115
+
116
+ when (text = @ss.scan(/\./))
117
+ action { [:DOT, text] }
118
+
100
119
  when (text = @ss.scan(/not(?![\w_:])/))
101
120
  action { [:NOT, text] }
102
121
 
@@ -112,9 +131,6 @@ class PuppetDB::Lexer < Racc::Parser
112
131
  when (text = @ss.scan(/false(?![\w_:])/))
113
132
  action { [:BOOLEAN, false]}
114
133
 
115
- when (text = @ss.scan(/-?\d+\.\d+/))
116
- action { [:NUMBER, text.to_f] }
117
-
118
134
  when (text = @ss.scan(/-?\d+/))
119
135
  action { [:NUMBER, text.to_i] }
120
136
 
@@ -130,6 +146,9 @@ class PuppetDB::Lexer < Racc::Parser
130
146
  when (text = @ss.scan(/@@/))
131
147
  action { [:EXPORTED, text] }
132
148
 
149
+ when (text = @ss.scan(/@/))
150
+ action { [:AT, text] }
151
+
133
152
  else
134
153
  text = @ss.string[@ss.pos .. -1]
135
154
  raise ScanError, "can not match: '" + text + "'"