puppet-debugger 0.4.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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +54 -0
  4. data/.gitlab-ci.yml +129 -0
  5. data/.rspec +3 -0
  6. data/CHANGELOG.md +61 -0
  7. data/Gemfile +18 -0
  8. data/Gemfile.lock +67 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +276 -0
  11. data/Rakefile +32 -0
  12. data/bin/pdb +4 -0
  13. data/lib/awesome_print/ext/awesome_puppet.rb +40 -0
  14. data/lib/puppet-debugger/cli.rb +247 -0
  15. data/lib/puppet-debugger/code/code_file.rb +98 -0
  16. data/lib/puppet-debugger/code/code_range.rb +69 -0
  17. data/lib/puppet-debugger/code/loc.rb +80 -0
  18. data/lib/puppet-debugger/debugger_code.rb +318 -0
  19. data/lib/puppet-debugger/support/compiler.rb +20 -0
  20. data/lib/puppet-debugger/support/environment.rb +38 -0
  21. data/lib/puppet-debugger/support/errors.rb +75 -0
  22. data/lib/puppet-debugger/support/facts.rb +78 -0
  23. data/lib/puppet-debugger/support/functions.rb +72 -0
  24. data/lib/puppet-debugger/support/input_responders.rb +136 -0
  25. data/lib/puppet-debugger/support/node.rb +90 -0
  26. data/lib/puppet-debugger/support/play.rb +91 -0
  27. data/lib/puppet-debugger/support/scope.rb +42 -0
  28. data/lib/puppet-debugger/support.rb +176 -0
  29. data/lib/puppet-debugger.rb +55 -0
  30. data/lib/trollop.rb +861 -0
  31. data/lib/version.rb +3 -0
  32. data/puppet-debugger.gemspec +36 -0
  33. data/run_container_test.sh +12 -0
  34. data/spec/facts_spec.rb +86 -0
  35. data/spec/fixtures/environments/production/manifests/site.pp +1 -0
  36. data/spec/fixtures/invalid_node_obj.yaml +8 -0
  37. data/spec/fixtures/node_obj.yaml +298 -0
  38. data/spec/fixtures/sample_manifest.pp +2 -0
  39. data/spec/fixtures/sample_start_debugger.pp +13 -0
  40. data/spec/pdb_spec.rb +50 -0
  41. data/spec/puppet-debugger_spec.rb +492 -0
  42. data/spec/remote_node_spec.rb +170 -0
  43. data/spec/spec_helper.rb +57 -0
  44. data/spec/support_spec.rb +190 -0
  45. data/test_matrix.rb +42 -0
  46. metadata +148 -0
@@ -0,0 +1,318 @@
1
+ require_relative 'code/loc'
2
+ require_relative 'code/code_range'
3
+ require_relative 'code/code_file'
4
+
5
+ # `Pry::Code` is a class that encapsulates lines of source code and their
6
+ # line numbers and formats them for terminal output. It can read from a file
7
+ # or method definition or be instantiated with a `String` or an `Array`.
8
+ #
9
+ # In general, the formatting methods in `Code` return a new `Code` object
10
+ # which will format the text as specified when `#to_s` is called. This allows
11
+ # arbitrary chaining of formatting methods without mutating the original
12
+ # object.
13
+ class DebuggerCode
14
+ class << self
15
+ # include MethodSource::CodeHelpers
16
+
17
+ # Instantiate a `Code` object containing code loaded from a file or
18
+ # Pry's line buffer.
19
+ #
20
+ # @param [String] filename The name of a file, or "(pry)".
21
+ # @param [Symbol] code_type The type of code the file contains.
22
+ # @return [Code]
23
+ def from_file(filename, code_type = nil)
24
+ code_file = CodeFile.new(filename, code_type)
25
+ new(code_file.code, 1, code_file.code_type)
26
+ end
27
+
28
+ # Instantiate a `Code` object containing code loaded from a file or
29
+ # Pry's line buffer.
30
+ #
31
+ # @param [String] source code".
32
+ # @param [Symbol] code_type The type of code the file contains.
33
+ # @return [Code]
34
+ def from_string(code, code_type = nil)
35
+ new(code, 1, code_type)
36
+ end
37
+ end
38
+
39
+ # @return [Symbol] The type of code stored in this wrapper.
40
+ attr_accessor :code_type
41
+
42
+ # Instantiate a `Code` object containing code from the given `Array`,
43
+ # `String`, or `IO`. The first line will be line 1 unless specified
44
+ # otherwise. If you need non-contiguous line numbers, you can create an
45
+ # empty `Code` object and then use `#push` to insert the lines.
46
+ #
47
+ # @param [Array<String>, String, IO] lines
48
+ # @param [Integer?] start_line
49
+ # @param [Symbol?] code_type
50
+ def initialize(lines = [], start_line = 1, code_type = :ruby)
51
+ if lines.is_a? String
52
+ lines = lines.lines
53
+ end
54
+ @lines = lines.each_with_index.map { |line, lineno|
55
+ LOC.new(line, lineno + start_line.to_i) }
56
+ @code_type = code_type
57
+
58
+ @with_marker = @with_indentation = nil
59
+ end
60
+
61
+ # Append the given line. +lineno+ is one more than the last existing
62
+ # line, unless specified otherwise.
63
+ #
64
+ # @param [String] line
65
+ # @param [Integer?] lineno
66
+ # @return [String] The inserted line.
67
+ def push(line, lineno = nil)
68
+ if lineno.nil?
69
+ lineno = @lines.last.lineno + 1
70
+ end
71
+ @lines.push(LOC.new(line, lineno))
72
+ line
73
+ end
74
+ alias << push
75
+
76
+ # Filter the lines using the given block.
77
+ #
78
+ # @yield [LOC]
79
+ # @return [Code]
80
+ def select(&block)
81
+ alter do
82
+ @lines = @lines.select(&block)
83
+ end
84
+ end
85
+
86
+ # Remove all lines that aren't in the given range, expressed either as a
87
+ # `Range` object or a first and last line number (inclusive). Negative
88
+ # indices count from the end of the array of lines.
89
+ #
90
+ # @param [Range, Integer] start_line
91
+ # @param [Integer?] end_line
92
+ # @return [Code]
93
+ def between(start_line, end_line = nil)
94
+ return self unless start_line
95
+
96
+ code_range = CodeRange.new(start_line, end_line)
97
+
98
+ alter do
99
+ @lines = @lines[code_range.indices_range(@lines)] || []
100
+ end
101
+ end
102
+
103
+ # Take `num_lines` from `start_line`, forward or backwards.
104
+ #
105
+ # @param [Integer] start_line
106
+ # @param [Integer] num_lines
107
+ # @return [Code]
108
+ def take_lines(start_line, num_lines)
109
+ start_idx =
110
+ if start_line >= 0
111
+ @lines.index { |loc| loc.lineno >= start_line } || @lines.length
112
+ else
113
+ [@lines.length + start_line, 0].max
114
+ end
115
+
116
+ alter do
117
+ @lines = @lines.slice(start_idx, num_lines)
118
+ end
119
+ end
120
+
121
+ # Remove all lines except for the +lines+ up to and excluding +lineno+.
122
+ #
123
+ # @param [Integer] lineno
124
+ # @param [Integer] lines
125
+ # @return [Code]
126
+ def before(lineno, lines = 1)
127
+ return self unless lineno
128
+
129
+ select do |loc|
130
+ loc.lineno >= lineno - lines && loc.lineno < lineno
131
+ end
132
+ end
133
+
134
+ # Remove all lines except for the +lines+ on either side of and including
135
+ # +lineno+.
136
+ #
137
+ # @param [Integer] lineno
138
+ # @param [Integer] lines
139
+ # @return [Code]
140
+ def around(lineno, lines = 1)
141
+ return self unless lineno
142
+
143
+ select do |loc|
144
+ loc.lineno >= lineno - lines && loc.lineno <= lineno + lines
145
+ end
146
+ end
147
+
148
+ # Remove all lines except for the +lines+ after and excluding +lineno+.
149
+ #
150
+ # @param [Integer] lineno
151
+ # @param [Integer] lines
152
+ # @return [Code]
153
+ def after(lineno, lines = 1)
154
+ return self unless lineno
155
+
156
+ select do |loc|
157
+ loc.lineno > lineno && loc.lineno <= lineno + lines
158
+ end
159
+ end
160
+
161
+ # Remove all lines that don't match the given `pattern`.
162
+ #
163
+ # @param [Regexp] pattern
164
+ # @return [Code]
165
+ def grep(pattern)
166
+ return self unless pattern
167
+ pattern = Regexp.new(pattern)
168
+
169
+ select do |loc|
170
+ loc.line =~ pattern
171
+ end
172
+ end
173
+
174
+ # Format output with line numbers next to it, unless `y_n` is falsy.
175
+ #
176
+ # @param [Boolean?] y_n
177
+ # @return [Code]
178
+ def with_line_numbers(y_n = true)
179
+ alter do
180
+ @with_line_numbers = y_n
181
+ end
182
+ end
183
+
184
+ # Format output with a marker next to the given +lineno+, unless +lineno+ is
185
+ # falsy.
186
+ #
187
+ # @param [Integer?] lineno
188
+ # @return [Code]
189
+ def with_marker(lineno = 1)
190
+ alter do
191
+ @with_marker = !!lineno
192
+ @marker_lineno = lineno
193
+ end
194
+ end
195
+
196
+ # Format output with the specified number of spaces in front of every line,
197
+ # unless `spaces` is falsy.
198
+ #
199
+ # @param [Integer?] spaces
200
+ # @return [Code]
201
+ def with_indentation(spaces = 0)
202
+ alter do
203
+ @with_indentation = !!spaces
204
+ @indentation_num = spaces
205
+ end
206
+ end
207
+
208
+ # @return [String]
209
+ def inspect
210
+ Object.instance_method(:to_s).bind(self).call
211
+ end
212
+
213
+ # @return [Integer] the number of digits in the last line.
214
+ def max_lineno_width
215
+ @lines.length > 0 ? @lines.last.lineno.to_s.length : 0
216
+ end
217
+
218
+ # @return [String] a formatted representation (based on the configuration of
219
+ # the object).
220
+ def to_s
221
+ print_to_output("", false)
222
+ end
223
+
224
+ # @return [String] a (possibly highlighted) copy of the source code.
225
+ def highlighted
226
+ print_to_output("", true)
227
+ end
228
+
229
+ # Writes a formatted representation (based on the configuration of the
230
+ # object) to the given output, which must respond to `#<<`.
231
+ def print_to_output(output, color=false)
232
+ @lines.each do |loc|
233
+ loc = loc.dup
234
+ loc.add_line_number(max_lineno_width) if @with_line_numbers
235
+ loc.add_marker(@marker_lineno) if @with_marker
236
+ loc.indent(@indentation_num) if @with_indentation
237
+ output << loc.line
238
+ output << "\n"
239
+ end
240
+ output
241
+ end
242
+
243
+ # Get the comment that describes the expression on the given line number.
244
+ #
245
+ # @param [Integer] line_number (1-based)
246
+ # @return [String] the code.
247
+ def comment_describing(line_number)
248
+ self.class.comment_describing(raw, line_number)
249
+ end
250
+
251
+ # Get the multiline expression that starts on the given line number.
252
+ #
253
+ # @param [Integer] line_number (1-based)
254
+ # @return [String] the code.
255
+ def expression_at(line_number, consume = 0)
256
+ self.class.expression_at(raw, line_number, :consume => consume)
257
+ end
258
+
259
+ # Get the multiline expression that starts on the given line number.
260
+ #
261
+ # @param [Integer] line_number (1-based)
262
+ # @return [String] the code.
263
+ def self.expression_at(raw, line_number, consume = 0)
264
+ #self.class.expression_at(raw, line_number, :consume => consume)
265
+ raw
266
+ end
267
+
268
+ # Get the (approximate) Module.nesting at the give line number.
269
+ #
270
+ # @param [Integer] line_number line number starting from 1
271
+ # @param [Module] top_module the module in which this code exists
272
+ # @return [Array<Module>] a list of open modules.
273
+ def nesting_at(line_number, top_module = Object)
274
+ Indent.nesting_at(raw, line_number)
275
+ end
276
+
277
+ # Return an unformatted String of the code.
278
+ #
279
+ # @return [String]
280
+ def raw
281
+ @lines.map(&:line).join("\n") << "\n"
282
+ end
283
+
284
+ # Return the number of lines stored.
285
+ #
286
+ # @return [Integer]
287
+ def length
288
+ @lines ? @lines.length : 0
289
+ end
290
+
291
+ # Two `Code` objects are equal if they contain the same lines with the same
292
+ # numbers. Otherwise, call `to_s` and `chomp` and compare as Strings.
293
+ #
294
+ # @param [Code, Object] other
295
+ # @return [Boolean]
296
+ def ==(other)
297
+ if other.is_a?(Code)
298
+ other_lines = other.instance_variable_get(:@lines)
299
+ @lines.each_with_index.all? { |loc, i| loc == other_lines[i] }
300
+ else
301
+ to_s.chomp == other.to_s.chomp
302
+ end
303
+ end
304
+
305
+ # Forward any missing methods to the output of `#to_s`.
306
+ def method_missing(name, *args, &block)
307
+ to_s.send(name, *args, &block)
308
+ end
309
+ undef =~
310
+
311
+ protected
312
+
313
+ # An abstraction of the `dup.instance_eval` pattern used throughout this
314
+ # class.
315
+ def alter(&block)
316
+ dup.tap { |o| o.instance_eval(&block) }
317
+ end
318
+ end
@@ -0,0 +1,20 @@
1
+ require 'tempfile'
2
+
3
+ module PuppetDebugger
4
+ module Support
5
+ module Compilier
6
+ def create_compiler(node)
7
+ Puppet::Parser::Compiler.new(node)
8
+ end
9
+
10
+ def compiler
11
+ @compiler
12
+ end
13
+
14
+ def set_compiler(value)
15
+ @compiler = value
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,38 @@
1
+ module PuppetDebugger
2
+ module Support
3
+ module Environment
4
+ # creates a puppet environment given a module path and environment name
5
+ # this is cached
6
+ def puppet_environment
7
+ @puppet_environment ||= create_environment
8
+ end
9
+
10
+ def create_environment
11
+ @puppet_environment = Puppet::Node::Environment.create(
12
+ default_puppet_env_name,
13
+ default_modules_paths,
14
+ default_manifests_dir
15
+ )
16
+ end
17
+
18
+ def set_environment(value)
19
+ @puppet_environment = value
20
+ end
21
+
22
+ def puppet_env_name
23
+ puppet_environment.name
24
+ end
25
+
26
+ # the cached name of the environment
27
+ def default_puppet_env_name
28
+ ENV['PUPPET_ENV'] || Puppet[:environment]
29
+ end
30
+
31
+ # currently this is not being used
32
+ def environment_loaders
33
+ name = compiler.loaders.public_environment_loader.loader_name
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,75 @@
1
+ module PuppetDebugger
2
+ module Exception
3
+ class Error < StandardError
4
+ attr_accessor :data
5
+ def initialize(data={})
6
+ @data = data
7
+ end
8
+
9
+ end
10
+
11
+ class FatalError < Error
12
+ end
13
+
14
+ class ConnectError < Error
15
+ def message
16
+ out = <<-EOF
17
+ #{data[:message]}
18
+ EOF
19
+ end
20
+ end
21
+
22
+ class BadFilter < FatalError
23
+ def message
24
+ data[:message]
25
+ end
26
+ end
27
+
28
+ class UndefinedNode < FatalError
29
+ def message
30
+ out = <<-EOF
31
+ Cannot find node with name: #{data[:name]} on remote server
32
+ EOF
33
+ end
34
+ end
35
+
36
+ class TimeOutError < Error
37
+ #Errno::ETIMEDOUT
38
+ end
39
+
40
+ class NoClassError < FatalError
41
+ def message
42
+ out = <<-EOF
43
+ #{data[:message]}
44
+ You are missing puppet classes that are required for compilation.
45
+ Please ensure these classes are installed on this machine in any of the following paths:
46
+ #{data[:default_modules_paths]}
47
+ EOF
48
+ end
49
+ end
50
+
51
+ class NodeDefinitionError < FatalError
52
+ def message
53
+ out = <<-EOF
54
+ You are missing a default node definition in your site.pp that is required for compilation.
55
+ Please ensure you have at least the following default node definition
56
+ node default {
57
+ # include classes here
58
+ }
59
+ in your #{data[:default_site_manifest]} file.
60
+ EOF
61
+ out.fatal
62
+ end
63
+ end
64
+
65
+ class AuthError < FatalError
66
+ def message
67
+ out = <<-EOF
68
+ #{data[:message]}
69
+ You will need to edit your auth.conf or conf.d/auth.conf (puppetserver) to allow node calls.
70
+ EOF
71
+ end
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,78 @@
1
+ module PuppetDebugger
2
+ module Support
3
+ module Facts
4
+ # in the future we will want to grab real facts from real systems via puppetdb
5
+ # or enc data
6
+
7
+ # allow the user to specify the facterdb filter
8
+ def dynamic_facterdb_filter
9
+ ENV['REPL_FACTERDB_FILTER'] || default_facterdb_filter
10
+ end
11
+
12
+ def default_facterdb_filter
13
+ "operatingsystem=#{facter_os_name} and operatingsystemrelease=#{facter_os_version} and architecture=x86_64 and facterversion=#{facter_version}"
14
+ end
15
+
16
+ def facter_version
17
+ ENV['REPL_FACTER_VERSION'] || default_facter_version
18
+ end
19
+
20
+ # return the correct supported version of facter facts
21
+ def default_facter_version
22
+ if Gem::Version.new(Puppet.version) >= Gem::Version.new(4.4)
23
+ '/^3\.1/'
24
+ else
25
+ '/^2\.4/'
26
+ end
27
+ end
28
+
29
+ def facter_os_name
30
+ ENV['REPL_FACTER_OS_NAME'] || 'Fedora'
31
+ end
32
+
33
+ def facter_os_version
34
+ ENV['REPL_FACTER_OS_VERSION'] || '23'
35
+ end
36
+
37
+ def set_facts(value)
38
+ @facts = value
39
+ end
40
+
41
+ # uses facterdb (cached facts) and retrives the facts given a filter
42
+ # creates a new facts object
43
+ # we could also use fact_merge to get real facts from the real system or puppetdb
44
+ def node_facts
45
+ node_facts = FacterDB.get_facts(dynamic_facterdb_filter).first
46
+ if node_facts.nil?
47
+ message = <<-EOS
48
+ Using filter: #{facterdb_filter}
49
+ Bad FacterDB filter, please change the filter so it returns a result set.
50
+ See https://github.com/camptocamp/facterdb/#with-a-string-filter
51
+ EOS
52
+ raise PuppetDebugger::Exception::BadFilter.new(:message => message)
53
+ end
54
+ # fix for when --show-legacy facts are not part of the facter 3 fact set
55
+ node_facts[:fqdn] = node_facts[:networking].fetch('fqdn',nil) unless node_facts[:fqdn]
56
+ node_facts
57
+ end
58
+
59
+ def default_facts
60
+ unless @facts
61
+ values = Hash[ node_facts.map { |k, v| [k.to_s, v] } ]
62
+ name = values['fqdn']
63
+ @facts ||= Puppet::Node::Facts.new(name, values)
64
+ end
65
+ @facts
66
+ end
67
+
68
+ def server_facts
69
+ data = {}
70
+ data["servername"] = Facter.value("fqdn") || Facter.value('networking')['fqdn']
71
+ data['serverip'] = Facter.value("ipaddress")
72
+ data["serverversion"] = Puppet.version.to_s
73
+ data
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,72 @@
1
+ module PuppetDebugger
2
+ module Support
3
+ module Functions
4
+ # returns a array of function files which is only required
5
+ # when displaying the function map, puppet will load each function on demand
6
+ # in the future we may want to utilize the puppet loaders to find these things
7
+ def function_files
8
+ search_dirs = lib_dirs.map do |lib_dir|
9
+ [File.join(lib_dir, 'puppet', 'functions', '**', '*.rb'),
10
+ File.join(lib_dir, 'functions', '**', '*.rb'),
11
+ File.join(lib_dir, 'puppet', 'parser', 'functions', '*.rb')
12
+ ]
13
+ end
14
+ # add puppet lib directories
15
+ search_dirs << [File.join(puppet_lib_dir, 'puppet', 'functions', '**', '*.rb'),
16
+ File.join(puppet_lib_dir, 'puppet', 'parser', 'functions', '*.rb')
17
+ ]
18
+ Dir.glob(search_dirs.flatten)
19
+ end
20
+
21
+ # returns either the module name or puppet version
22
+ def mod_finder
23
+ @mod_finder ||= Regexp.new('\/([\w\-\.]+)\/lib')
24
+ end
25
+
26
+ # returns a map of functions
27
+ def function_map
28
+ unless @functions
29
+ do_initialize
30
+ @functions = {}
31
+ function_files.each do |file|
32
+ obj = {}
33
+ name = File.basename(file, '.rb')
34
+ obj[:name] = name
35
+ obj[:parent] = mod_finder.match(file)[1]
36
+ @functions["#{obj[:parent]}::#{name}"] = obj
37
+ end
38
+ end
39
+ @functions
40
+ end
41
+
42
+ # returns an array of module loaders that we may need to use in the future
43
+ # in order to parse all types of code (ie. functions) For now this is not
44
+ # being used.
45
+ def resolve_paths(loaders)
46
+ mod_resolver = loaders.instance_variable_get(:@module_resolver)
47
+ all_mods = mod_resolver.instance_variable_get(:@all_module_loaders)
48
+ end
49
+
50
+ # gather all the lib dirs
51
+ def lib_dirs
52
+ dirs = modules_paths.map do |mod_dir|
53
+ Dir["#{mod_dir}/*/lib"].entries
54
+ end.flatten
55
+ dirs + [puppet_repl_lib_dir]
56
+ end
57
+
58
+ # load all the lib dirs so puppet can find the functions
59
+ # at this time, this function is not being used
60
+ def load_lib_dirs
61
+ lib_dirs.each do |lib|
62
+ $LOAD_PATH << lib
63
+ end
64
+ end
65
+
66
+ # def functions
67
+ # @functions = []
68
+ # @functions << compiler.loaders.static_loader.loaded.keys.find_all {|l| l.type == :function}
69
+ # end
70
+ end
71
+ end
72
+ end