puppetdb_query 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 483f7ff2212fc5b4b01df7c5e743848648e7796e
4
+ data.tar.gz: 86cecf8ac71a8ee6281f351c76a4ac10460def0e
5
+ SHA512:
6
+ metadata.gz: 830b95936dd945d8540ee67a643d31faca45f4454b37eb2f2983809bed15d8e7594182fe29b84eee1e33977e30e97d45799dd0403752a146b32e61ce0c5321e7
7
+ data.tar.gz: 03b48691e946b2ecf0fc129e7a78a57c16f75c0d7fb267a843e8dc9b502d64cc80a5785a17089bae320c2b1d717171aa26058331c521c680a927b1c0d68b5eca
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.idea/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /*.gem
6
+ /tester.rb
7
+ /test/
8
+ /_yardoc/
9
+ /coverage/
10
+ /doc/
11
+ /pkg/
12
+ /spec/reports/
13
+ /tmp/
data/.rubocop.yml ADDED
@@ -0,0 +1,102 @@
1
+ # This yaml file describes which files are excluded in rubocop run.
2
+ # It can be in the project home directory or in the $HOME folder.
3
+
4
+ # Common configuration.
5
+ AllCops:
6
+ Include:
7
+ - 'lib/**/'
8
+ - 'exe/*'
9
+
10
+ Exclude:
11
+ - 'scripts/**/*'
12
+ - 'vendor/**/*'
13
+ - 'bin/**/*'
14
+ - 'bundle/**/*'
15
+ - 'local-gems/**/*'
16
+ - '**/*.sh'
17
+ - '**/Gemfile'
18
+ - 'tester.rb'
19
+ - 'test/**/*'
20
+
21
+ # --- XXXLength-Section --------------------------------------------------------------------
22
+ # too long lines, methods and classes are annoying,
23
+ LineLength:
24
+ Enabled: true
25
+ Max: 100
26
+
27
+ MethodLength:
28
+ Enabled: true
29
+ Max: 35
30
+
31
+ Metrics/AbcSize:
32
+ Max: 40
33
+
34
+ ClassLength:
35
+ Enabled: true
36
+ Max: 140
37
+
38
+ # --- Style Cops - Section -----------------------------------------------------------------
39
+ # Don't be so dogmatic about Hash-Style! Both are fine for us
40
+ HashSyntax:
41
+ Enabled: true
42
+
43
+ # From Ruby 2.x on there is no need for this anymore, so why bothering now?
44
+ Encoding:
45
+ Enabled: false
46
+
47
+ # Ensable following message: Documentation: Missing top-level class documentation comment.
48
+ Documentation:
49
+ Enabled: true
50
+
51
+ # check filename conventions
52
+ FileName:
53
+ Enabled: true
54
+
55
+ # this 3-digit thing for portnumbers? oh, come on!
56
+ NumericLiterals:
57
+ Enabled: false
58
+
59
+ # ok, one should avoid global vars, but from time to time we need them
60
+ Style/GlobalVars:
61
+ Enabled: true
62
+
63
+ Style/RegexpLiteral:
64
+ Enabled: true
65
+
66
+ Style/AlignParameters:
67
+ EnforcedStyle: "with_fixed_indentation"
68
+
69
+ Style/BracesAroundHashParameters:
70
+ EnforcedStyle: "context_dependent"
71
+
72
+ Style/EachWithObject:
73
+ Enabled: false
74
+
75
+ # we now the special global variables by heart
76
+ Style/SpecialGlobalVars:
77
+ Enabled: false
78
+
79
+ # we don't care about quoting style
80
+ Style/StringLiterals:
81
+ Enabled: false
82
+
83
+ # for easier line moving
84
+ Style/TrailingCommaInLiteral:
85
+ Enabled: false
86
+
87
+
88
+ # --- Complexity - Section -----------------------------------------------------------------
89
+ # as old McCabe says:
90
+ #
91
+ # Cyclomatic Complexity Risk Evaluation...
92
+ # 1-10 A simple module without much risk
93
+ # 11-20 A more complex module with moderate risk
94
+ # 21-50 A complex module of high risk
95
+ # 51 and greater An untestable program of very high risk
96
+ CyclomaticComplexity:
97
+ Max: 10
98
+
99
+ # Lint-Section -----------------------------------------------------------------------------
100
+ # what is soooo bad about blablubb.match /..../ compared to blablubb.match(/..../)?
101
+ Lint/AmbiguousRegexpLiteral:
102
+ Enabled: true
data/.travis.yml ADDED
@@ -0,0 +1,19 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.9
8
+ - 2.2.5
9
+ - 2.3.1
10
+ - jruby-19mode
11
+ - jruby-head
12
+ - rbx
13
+ - rbx-19mode
14
+ matrix:
15
+ fast_finish: true
16
+ allow_failures:
17
+ - rvm: jruby-head
18
+ - rvm: rbx
19
+ - rvm: rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ # only for local testing but not needed for spec tests
6
+ group :test do
7
+ gem 'ruby-puppetdb', '=1.5.3' if RUBY_VERSION !~ /^1\./
8
+ gem 'puppet', '=3.8.7' if RUBY_VERSION !~ /^1\./
9
+ gem "rubocop", '=0.39.0' if RUBY_VERSION =~ /^1\./
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Michael Meyling
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # puppetdb_query - query puppetdb data from other sources
2
+
3
+ Just store and update your puppet facts also in another database and query nodes or facts from that other database.
4
+ This can speed up your queries enormously and reduce the load on your puppet database.
5
+
6
+ ## General
7
+
8
+ The puppet database schema is not designed for complicated queries on numerous nodes. Here we provide
9
+ an implementation for storing and querying node facts in a mongodb.
10
+
11
+ You must simply establish a sync job to read the data from your puppetdb and write it to a mongodb.
12
+
13
+ Currently the implementation supports only puppetdb api V 4.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'puppetdb_query'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install puppetdb_query
30
+
31
+ ## Usage within program
32
+
33
+ ```ruby
34
+ require "mongo"
35
+ require "puppetdb_query"
36
+ require "pp"
37
+
38
+ MONGO_HOSTS = ['mongo.myhost.org:27017']
39
+ MONGO_OPTIONS = { database: 'puppetdb', user: 'ops', password: 'very secret' }
40
+
41
+ pm = PuppetDBQuery::MongoQuery.new(MONGO_HOSTS, MONGO_OPTIONS)
42
+ pm.nodes({processorcount: '4', lvm_support: true})
43
+ pm.facts({processorcount: '4', lvm_support: true}, ["macaddress", "operatingsystem"])
44
+
45
+ mongo = PuppetDBQuery::ToMongo.new
46
+ query = mongo.query("processorcount='4' and lvm_support=true")
47
+ pp query
48
+ pm.nodes(query)
49
+ pm.facts(query, ["macaddress", "operatingsystem"])
50
+ ```
51
+
52
+ ## Development
53
+
54
+ After checking out the repo, run `bundle install` to install dependencies.
55
+
56
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it ( https://github.com/m-31/puppetdb_query/fork )
61
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
63
+ 4. Push to the branch (`git push origin my-new-feature`)
64
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'rake'
2
+
3
+ require "rubocop/rake_task"
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run RuboCop on the lib directory"
7
+ RuboCop::RakeTask.new(:rubocop) do |task|
8
+ task.formatters = ["fuubar"]
9
+ task.options = ["-D"]
10
+ task.options = task.options + ["--fail-level", "E"] if RUBY_VERSION =~ /^1\./
11
+ task.fail_on_error = true
12
+ end
13
+
14
+ RSpec::Core::RakeTask.new(:spec) do |t|
15
+ t.pattern = 'spec/**/*_spec.rb'
16
+ end
17
+
18
+ task :default => ["spec", "rubocop"]
19
+
@@ -0,0 +1,45 @@
1
+ require 'logger'
2
+
3
+ module PuppetDBQuery
4
+ # for logger access just include this module
5
+ module Logging
6
+ class << self
7
+ attr_writer :logger
8
+
9
+ def logger
10
+ unless @logger
11
+ @logger = Logger.new($stdout)
12
+ @logger.level = (ENV['LOG_LEVEL'] || Logger::DEBUG).to_i
13
+ end
14
+ @logger
15
+ end
16
+ end
17
+
18
+ # addition
19
+ def self.included(base)
20
+ # rubocop:disable Lint/NestedMethodDefinition
21
+ class << base
22
+ def logger
23
+ # :nocov:
24
+ Logging.logger
25
+ # :nocov:
26
+ end
27
+
28
+ def logger=(logger)
29
+ # :nocov:
30
+ Logging.logger = logger
31
+ # :nocov:
32
+ end
33
+ end
34
+ # rubocop:enable Lint/NestedMethodDefinition
35
+ end
36
+
37
+ def logger
38
+ Logging.logger
39
+ end
40
+
41
+ def logger=(logger)
42
+ Logging.logger = logger
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,134 @@
1
+ require 'time'
2
+
3
+ require_relative "logging"
4
+
5
+ module PuppetDBQuery
6
+ # access nodes and their facts from mongo database
7
+ class MongoDB
8
+ include Logging
9
+ attr_reader :connection
10
+ attr_reader :nodes_collection
11
+ attr_reader :nodes_properties_collection
12
+ attr_reader :meta_collection
13
+
14
+ # initialize access to mongodb
15
+ #
16
+ # You might want to adjust the logging level, for example:
17
+ # ::Mongo::Logger.logger.level = logger.level
18
+ #
19
+ # @param connection mongodb connection, should already be switched to correct database
20
+ # @param nodes symbol for collection that contains nodes with their facts
21
+ # @param nodes_properties symbol for collection for nodes with their update timestamps
22
+ # @param meta symbol for collection with update metadata
23
+ def initialize(connection, nodes = :nodes, nodes_properties = :nodes_properties, meta = :meta)
24
+ @connection = connection
25
+ @nodes_collection = nodes
26
+ @nodes_propeties_collection = nodes_properties
27
+ @meta_collection = meta
28
+ end
29
+
30
+ # get node names that fulfill given mongodb query
31
+ def query_nodes(query)
32
+ collection = connection[nodes_collection]
33
+ collection.find(query).batch_size(999).projection(_id: 1).map { |k| k[:_id] }
34
+ end
35
+
36
+ # get nodes and their facts that fulfill given mongodb query
37
+ def query_facts(query, facts)
38
+ fields = Hash[facts.collect { |fact| [fact.to_sym, 1] }]
39
+ collection = connection[nodes_collection]
40
+ result = {}
41
+ collection.find(query).batch_size(999).projection(fields).each do |values|
42
+ id = values.delete('_id')
43
+ result[id] = values
44
+ end
45
+ result
46
+ end
47
+
48
+ # get all node names
49
+ def nodes
50
+ collection = connection[nodes_collection]
51
+ collection.find.batch_size(999).projection(_id: 1).map { |k| k[:_id] }
52
+ end
53
+
54
+ # get facts for given node name
55
+ def node_facts(node)
56
+ collection = connection[nodes_collection]
57
+ result = collection.find(_id: node).limit(999).batch_size(999).to_a.first
58
+ result.delete("_id") if result
59
+ result
60
+ end
61
+
62
+ # get all nodes and their facts
63
+ def facts
64
+ collection = connection[nodes_collection]
65
+ result = {}
66
+ collection.find.batch_size(999).each do |values|
67
+ id = values.delete('_id')
68
+ result[id] = values
69
+ end
70
+ result
71
+ end
72
+
73
+ # update or insert facts for given node name
74
+ def node_update(node, facts)
75
+ connection[nodes_collection].find(_id: node).replace_one(facts, upsert: true)
76
+ rescue ::Mongo::Error::OperationFailure => e
77
+ # mongodb doesn't support keys with a dot
78
+ # see https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
79
+ # as a dirty workaround we delete the document and insert it ;-)
80
+ # The dotted field .. in .. is not valid for storage. (57)
81
+ raise e unless e.message =~ /The dotted field /
82
+ connection[nodes_collection].find(_id: node).delete_one
83
+ connection[nodes_collection].insert_one(facts.merge(_id: node))
84
+ end
85
+
86
+ # delete node data for given node name
87
+ def node_delete(node)
88
+ connection[nodes_collection].find(_id: node).delete_one
89
+ end
90
+
91
+ # update node properties
92
+ def node_properties_update(new_node_properties, ts_begin)
93
+ collection = connection[nodes_properties_collection]
94
+ old_names = collection.find.batch_size(999).projection(_id: 1).map { |k| k[:_id] }
95
+ delete = old_names - new_node_properties.keys
96
+ collection.insert_many(nodes_properties.map { |k, v| v.dup.tap { v[:_id] = k } })
97
+ collection.delete_many(_id: { '$in' => delete })
98
+ ts_end = Time.iso8601(Time.now)
99
+ connection[meta_collection].find_one_and_update(
100
+ {},
101
+ {
102
+ '$set' => {
103
+ last_node_properties_update: {
104
+ ts_begin: ts_begin,
105
+ ts_end: ts_end
106
+ }
107
+ }
108
+ },
109
+ { upsert: true }
110
+ )
111
+ end
112
+
113
+ # update or insert timestamps for given fact update method
114
+ def meta_fact_update(method, ts_begin, ts_end)
115
+ connection[meta_collection].find_one_and_update(
116
+ {},
117
+ {
118
+ '$set' => {
119
+ last_fact_update: {
120
+ ts_begin: ts_begin,
121
+ ts_end: ts_end,
122
+ method: method
123
+ },
124
+ method => {
125
+ ts_begin: ts_begin,
126
+ ts_end: ts_end
127
+ }
128
+ }
129
+ },
130
+ { upsert: true }
131
+ )
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,38 @@
1
+ require_relative "tokenizer"
2
+
3
+ module PuppetDBQuery
4
+ # operator with priority and representation information
5
+ class Operator
6
+ attr_reader :symbol
7
+ attr_reader :infix
8
+ attr_reader :priority
9
+ attr_reader :minimum
10
+ attr_reader :maximum
11
+ attr_reader :string
12
+
13
+ def initialize(symbol, infix, priority, minimum, maximum = nil)
14
+ @symbol = symbol
15
+ @infix = infix
16
+ @priority = priority
17
+ @minimum = minimum
18
+ @maximum = maximum
19
+ @string = Tokenizer.symbol_to_string(symbol)
20
+ end
21
+
22
+ def infix?
23
+ infix
24
+ end
25
+
26
+ def prefix?
27
+ !infix
28
+ end
29
+
30
+ def ==(other)
31
+ other.class == self.class && other.symbol == symbol
32
+ end
33
+
34
+ def to_s
35
+ @string
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,186 @@
1
+ require_relative "operator"
2
+ require_relative "term"
3
+ require_relative "tokenizer"
4
+ require_relative "logging"
5
+
6
+ module PuppetDBQuery
7
+ # parse a puppetdb query string into #PuppetDBQuery::Term s
8
+ class Parser
9
+ include Logging
10
+
11
+ def self.parse(puppetdb_query)
12
+ Parser.new.parse(puppetdb_query)
13
+ end
14
+
15
+ # these are the operators we understand
16
+ # rubocop:disable Style/ExtraSpacing
17
+ AND = Operator.new(:and, true, 100, 2)
18
+ OR = Operator.new(:or, true, 90, 2)
19
+ NOT = Operator.new(:not, false, 1, 1, 1)
20
+ EQUAL = Operator.new(:equal, true, 200, 2, 2)
21
+ NOT_EQUAL = Operator.new(:not_equal, true, 200, 2, 2)
22
+ MATCH = Operator.new(:match, true, 200, 2, 2)
23
+ # rubocop:enable Style/ExtraSpacing
24
+
25
+ # map certain symbols (we get them from a tokenizer) to our operators
26
+ OPERATORS = {
27
+ AND.symbol => AND,
28
+ OR.symbol => OR,
29
+ NOT.symbol => NOT,
30
+ EQUAL.symbol => EQUAL,
31
+ NOT_EQUAL.symbol => NOT_EQUAL,
32
+ MATCH.symbol => MATCH,
33
+ }.freeze
34
+
35
+ attr_reader :symbols # array of symbols
36
+ attr_reader :position # current parsing position in array of symbols
37
+
38
+ # parse query and get resulting array of PuppetDBQuery::Term s
39
+ def parse(query)
40
+ @symbols = Tokenizer.symbols(query)
41
+ @position = 0
42
+ r = []
43
+ r << read_maximal_term(0) until empty?
44
+ r
45
+ end
46
+
47
+ private
48
+
49
+ # Reads next maximal term. The following input doesn't make the term ore complete.
50
+ # Respects the priority of operators by comparing it to the given value.
51
+ def read_maximal_term(priority)
52
+ return nil if empty?
53
+ first = read_minimal_term
54
+ term = add_next_infix_terms(priority, first)
55
+ logger.debug "read maximal term: #{term}"
56
+ term
57
+ end
58
+
59
+ # Read next following term. This is a complete term but some infix operator
60
+ # or some terms for an infix operator might follow.
61
+ # rubocop:disable Metrics/PerceivedComplexity
62
+ def read_minimal_term
63
+ term = nil
64
+ operator = get_operator
65
+ if operator
66
+ error("'#{operator}' is no prefix operator") unless operator.prefix?
67
+ read_token
68
+ term = Term.new(operator)
69
+ arg = read_maximal_term(operator.priority)
70
+ term.add(arg)
71
+ logger.debug "read_minimal_term: #{term}"
72
+ return term
73
+ end
74
+ # no prefix operator found
75
+ token = get_token
76
+ if token == :begin
77
+ read_token
78
+ term = read_maximal_term(0)
79
+ error "'#{Tokenizer.symbol_to_string(:end)}' expected " unless read_token == :end
80
+ elsif token == :list_begin
81
+ read_token
82
+ term = read_maximal_term(0)
83
+ error "'#{Tokenizer.symbol_to_string(:list_end)}' expected " unless read_token == :list_end
84
+ else
85
+ error("no operator #{get_operator} expected here") if get_operator
86
+ token = read_token
87
+ logger.debug "atom found: #{token}"
88
+ term = token
89
+ end
90
+ logger.debug "read minimal term: #{term}"
91
+ term
92
+ end
93
+ # rubocop:enable Metrics/PerceivedComplexity
94
+
95
+ # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
96
+ # rubocop:disable Metrics/MethodLength,Metrics/PerceivedComplexity
97
+ def add_next_infix_terms(priority, first)
98
+ old_operator = nil
99
+ term = first
100
+ loop do
101
+ # we expect an infix operator
102
+ operator = get_operator
103
+ logger.debug "we found operator '#{operator}'" if operator
104
+ if operator.nil? || operator.prefix? || operator.priority <= priority
105
+ logger.debug "'#{operator}' is prefex '#{operator && operator.prefix?}' or has less" \
106
+ " priority #{operator && operator.priority} than #{priority}"
107
+ logger.debug "get_next_infix_terms: #{term}"
108
+ return term
109
+ end
110
+ if old_operator.nil? || old_operator.priority >= operator.priority
111
+ # old operator has not less priority
112
+ read_token
113
+ new_term = read_maximal_term(operator.priority)
114
+ error("to few arguments for operator '#{operator}'") if new_term.nil?
115
+ logger.debug "is '#{old_operator}' == '#{operator}' : #{old_operator == operator}"
116
+ if old_operator == operator
117
+ if operator.maximum && term.args.size + 1 >= operator.maximum
118
+ raise "to much arguments for operator '#{operator}'"
119
+ end
120
+ term.add(new_term)
121
+ else
122
+ also_new_term = Term.new(operator)
123
+ also_new_term.add(term)
124
+ also_new_term.add(new_term)
125
+ term = also_new_term
126
+ end
127
+ else
128
+ # old operator has less priority
129
+ new_term = read_maximal_term(operator.priority)
130
+ error("to few arguments for operator '#{operator}'") if new_term.nil?
131
+ also_new_term = Term.new(operator)
132
+ also_new_term.add(term)
133
+ also_new_term.add(new_term)
134
+ term = also_new_term
135
+ end
136
+ old_operator = operator
137
+ end
138
+ end
139
+ # rubocop:enable Metrics/AbcSize,Metrics/CyclomaticComplexity
140
+ # rubocop:enable Metrics/MethodLength,Metrics/PerceivedComplexity
141
+
142
+ # rubocop:disable Style/AccessorMethodName
143
+ def get_operator
144
+ OPERATORS[get_token]
145
+ end
146
+ # rubocop:enable Style/AccessorMethodName
147
+
148
+ def read_token
149
+ return nil if empty?
150
+ token = symbols[position]
151
+ @position += 1
152
+ token
153
+ end
154
+
155
+ def empty?
156
+ position >= symbols.size
157
+ end
158
+
159
+ # rubocop:disable Style/AccessorMethodName
160
+ def get_token
161
+ return nil if empty?
162
+ symbols[position]
163
+ end
164
+ # rubocop:enable Style/AccessorMethodName
165
+
166
+ def error(message)
167
+ length = Tokenizer.query(symbols[0..position]).size
168
+ raise "parsing query failed\n#{message}\n\n#{Tokenizer.query(symbols)}\n#{' ' * length}^"
169
+ end
170
+ end
171
+ end
172
+
173
+ if $0 == __FILE__
174
+ require "pp"
175
+ query = "facts=-7.4E1 and fucts=8 and fits=true or application_vertical='ops' and" \
176
+ " (application_group=\"live\"or application_group='prelive-production')"
177
+ puts query
178
+ query = PuppetDBQuery::Tokenizer.idem(query)
179
+ puts query
180
+ query = PuppetDBQuery::Tokenizer.idem(query)
181
+ puts query
182
+
183
+ parser = PuppetDBQuery::Parser.new
184
+ tree = parser.parse(query)
185
+ pp tree
186
+ end