puppet-debugger 0.4.0

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