Class: PuppetDebugger::Cli

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Support
Defined in:
lib/puppet-debugger/cli.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Support

#do_initialize, #generate_ast, #initialize_from_scope, #known_resource_types, #parse_error, #parser, #puppet_eval, #puppet_lib_dir, #puppet_repl_lib_dir, #static_responder_list

Methods included from Support::Loader

#all_data_types, #core_datatypes, #create_loader, #environment_data_types, #loaders

Methods included from Support::Node

#convert_remote_node, #create_node, #create_real_node, #get_remote_node, #node, #remote_node_name, #remote_node_name=, #set_node, #set_node_from_name, #set_remote_node_name

Methods included from Support::Functions

#data_type_files, #function_files, #function_map, #lib_dirs, #load_lib_dirs, #mod_finder

Methods included from Support::Scope

#create_scope, #scope, #scope_vars, #set_scope

Methods included from Support::Facts

#default_facter_version, #default_facterdb_filter, #default_facts, #dynamic_facterdb_filter, #facter_os_name, #facter_os_version, #facter_version, #node_facts, #server_facts, #set_facts

Methods included from Support::Environment

#create_environment, #create_node_environment, #default_manifests_dir, #default_modules_paths, #default_puppet_env_name, #default_site_manifest, #environment_loaders, #modules_paths, #puppet_env_name, #puppet_environment, #set_environment

Methods included from Support::Compilier

#compiler, #create_compiler, #set_compiler

Constructor Details

#initialize(options = {}) ⇒ Cli

Returns a new instance of Cli.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/puppet-debugger/cli.rb', line 19

def initialize(options = {})
  do_initialize if Puppet[:codedir].nil?
  Puppet.settings[:name] = :debugger
  Puppet.settings[:trusted_server_facts] = true unless Puppet.settings[:trusted_server_facts].nil?
  Puppet[:static_catalogs] = false unless Puppet.settings[:static_catalogs].nil?
  set_remote_node_name(options[:node_name])
  initialize_from_scope(options[:scope])
  @log_level = 'notice'
  @out_buffer = options[:out_buffer] || $stdout
  @html_mode = options[:html_mode] || false
  @source_file = options[:source_file] || nil
  @source_line_num = options[:source_line] || nil
  @in_buffer = options[:in_buffer] || $stdin
  comp = proc do |s|
    key_words.grep(/^#{Regexp.escape(s)}/)
  end
  Readline.completion_append_character = ''
  Readline.basic_word_break_characters = ' '
  Readline.completion_proc = comp
  AwesomePrint.defaults = {
    html: @html_mode,
    sort_keys: true,
    indent: 2
  }
end

Instance Attribute Details

#benchObject

Returns the value of attribute bench.



15
16
17
# File 'lib/puppet-debugger/cli.rb', line 15

def bench
  @bench
end

#extra_promptObject

Returns the value of attribute extra_prompt.



15
16
17
# File 'lib/puppet-debugger/cli.rb', line 15

def extra_prompt
  @extra_prompt
end

#hooksObject (readonly)

Returns the value of attribute hooks.



16
17
18
# File 'lib/puppet-debugger/cli.rb', line 16

def hooks
  @hooks
end

#html_modeObject

Returns the value of attribute html_mode.



15
16
17
# File 'lib/puppet-debugger/cli.rb', line 15

def html_mode
  @html_mode
end

#in_bufferObject

Returns the value of attribute in_buffer.



15
16
17
# File 'lib/puppet-debugger/cli.rb', line 15

def in_buffer
  @in_buffer
end

#log_levelObject

Returns the value of attribute log_level.



15
16
17
# File 'lib/puppet-debugger/cli.rb', line 15

def log_level
  @log_level
end

#out_bufferObject

Returns the value of attribute out_buffer.



15
16
17
# File 'lib/puppet-debugger/cli.rb', line 15

def out_buffer
  @out_buffer
end

#settingsObject

Returns the value of attribute settings.



15
16
17
# File 'lib/puppet-debugger/cli.rb', line 15

def settings
  @settings
end

#source_fileObject (readonly)

Returns the value of attribute source_file.



16
17
18
# File 'lib/puppet-debugger/cli.rb', line 16

def source_file
  @source_file
end

#source_line_numObject (readonly)

Returns the value of attribute source_line_num.



16
17
18
# File 'lib/puppet-debugger/cli.rb', line 16

def source_line_num
  @source_line_num
end

Class Method Details



156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/puppet-debugger/cli.rb', line 156

def self.print_repl_desc
  output = "Ruby Version: \#{RUBY_VERSION}\nPuppet Version: \#{Puppet.version}\nPuppet Debugger Version: \#{PuppetDebugger::VERSION}\nCreated by: NWOps <[email protected]>\nType \"commands\" for a list of debugger commands\nor \"help\" to show the help screen.\n\n\n  EOT\n  output\nend\n"

.start(options = { scope: nil }) ⇒ Object

start reads from stdin or from a file if from stdin, the repl will process the input and exit if from a file, the repl will process the file and continue to prompt

Parameters:

  • puppet (Hash)

    scope object



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/puppet-debugger/cli.rb', line 228

def self.start(options = { scope: nil })
  opts = Trollop.options do
    opt :play, 'Url or file to load from', required: false, type: String
    opt :run_once, 'Evaluate and quit', required: false, default: false
    opt :node_name, 'Remote Node to grab facts from', required: false, type: String
    opt :quiet, 'Do not display banner', required: false, default: false
  end
  options = opts.merge(options)
  puts print_repl_desc unless options[:quiet]
  options[:play] = options[:play].path if options[:play].respond_to?(:path)
  repl_obj = PuppetDebugger::Cli.new(options)
  if options[:play]
    repl_obj.handle_input("play #{options[:play]}")
  elsif ARGF.filename != '-'
    # when the user supplied a file name without using the args (stdin)
    path = File.expand_path(ARGF.filename)
    repl_obj.handle_input("play #{path}")
  elsif (ARGF.filename == '-') && (!STDIN.tty? && !STDIN.closed?)
    # when the user supplied a file content using stdin, aka. cat,pipe,echo or redirection
    input = ARGF.read
    repl_obj.handle_input(input)
  end
  # helper code to make tests exit the loop
  repl_obj.read_loop unless options[:run_once]
end

.start_without_stdin(options = { scope: nil }) ⇒ Object

used to start a debugger session without attempting to read from stdin or this is primarily used by the debug::break() module function and the puppet debugger face

Parameters:

  • must (Hash)

    contain at least the puppet scope object

  • play (Hash)

    a customizable set of options



214
215
216
217
218
219
220
221
222
# File 'lib/puppet-debugger/cli.rb', line 214

def self.start_without_stdin(options = { scope: nil })
  puts print_repl_desc unless options[:quiet]
  repl_obj = PuppetDebugger::Cli.new(options)
  options[:play] = options[:play].path if options[:play].respond_to?(:path)
  # TODO: make the output optional so we can have different output destinations
  repl_obj.handle_input('whereami') if options[:source_file] && options[:source_line]
  repl_obj.handle_input("play #{options[:play]}") if options[:play]
  repl_obj.read_loop unless options[:run_once]
end

Instance Method Details

#expand_resource_type(types) ⇒ Object

returns a formatted array



81
82
83
84
85
86
87
88
89
90
# File 'lib/puppet-debugger/cli.rb', line 81

def expand_resource_type(types)
  output = [types].flatten.map do |t|
    if t.class.to_s =~ /Puppet::Pops::Types/
      to_resource_declaration(t)
    else
      t
    end
  end
  output
end

#handle_input(input) ⇒ Object

this method handles all input and expects a string of text.

Raises:

  • (ArgumentError)


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/puppet-debugger/cli.rb', line 109

def handle_input(input)
  raise ArgumentError unless input.instance_of?(String)
  begin
    output = ''
    case input.strip
    when PuppetDebugger::InputResponders::Commands.command_list_regex
      args = input.split(' ')
      command = args.shift
      plugin = PuppetDebugger::InputResponders::Commands.plugin_from_command(command)
      output = plugin.execute(args, self)
      return out_buffer.puts output
    when '_'
      output = " => #{@last_item}"
    else
      result = puppet_eval(input)
      @last_item = result
      output = normalize_output(result)
      output = output.nil? ? '' : output.ai
    end
  rescue PuppetDebugger::Exception::InvalidCommand => e
    output = e.message.fatal
  rescue LoadError => e
    output = e.message.fatal
  rescue Errno::ETIMEDOUT => e
    output = e.message.fatal
  rescue ArgumentError => e
    output = e.message.fatal
  rescue Puppet::ResourceError => e
    output = e.message.fatal
  rescue Puppet::Error => e
    output = e.message.fatal
  rescue Puppet::ParseErrorWithIssue => e
    output = e.message.fatal
  rescue PuppetDebugger::Exception::FatalError => e
    output = e.message.fatal
    out_buffer.puts output
    exit 1 # this can sometimes causes tests to fail
  rescue PuppetDebugger::Exception::Error => e
    output = e.message.fatal
  end
  unless output.empty?
    out_buffer.print ' => '
    out_buffer.puts output unless output.empty?
    exec_hook :after_output, out_buffer, self, self
  end
end

#key_wordsObject

returns a cached list of key words



50
51
52
53
54
55
56
57
58
59
# File 'lib/puppet-debugger/cli.rb', line 50

def key_words
  # because dollar signs don't work we can't display a $ sign in the keyword
  # list so its not explicitly clear what the keyword
  variables = scope.to_hash.keys
  # prepend a :: to topscope variables
  scoped_vars = variables.map { |k, _v| scope.compiler.topscope.exist?(k) ? "$::#{k}" : "$#{k}" }
  # append a () to functions so we know they are functions
  funcs = function_map.keys.map { |k| "#{k.split('::').last}()" }
  (scoped_vars + funcs + static_responder_list + all_data_types).uniq.sort
end

#multiline_input?(e) ⇒ Boolean

tries to determine if the input is going to be a multiline input by reading the parser error message

Returns:

  • (Boolean)


172
173
174
175
176
177
178
179
# File 'lib/puppet-debugger/cli.rb', line 172

def multiline_input?(e)
  case e.message
  when /Syntax error at end of file/i
    true
  else
    false
  end
end

#normalize_output(result) ⇒ Object



92
93
94
95
96
97
98
99
100
101
# File 'lib/puppet-debugger/cli.rb', line 92

def normalize_output(result)
  if result.instance_of?(Array)
    output = expand_resource_type(result)
    return output.first if output.count == 1
    return output
  elsif result.class.to_s =~ /Puppet::Pops::Types/
    return to_resource_declaration(result)
  end
  result
end

#read_loopObject

reads input from stdin, since readline requires a tty we cannot read from other sources as readline requires a file object we parse the string after each input to determine if the input is a multiline_input entry. If it is multiline we run through the loop again and concatenate the input



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/puppet-debugger/cli.rb', line 186

def read_loop
  line_number = 1
  full_buffer = ''
  while buf = Readline.readline("#{line_number}:#{extra_prompt}>> ", true)
    begin
      full_buffer += buf
      # unless this is puppet code, otherwise skip repl keywords
      unless PuppetDebugger::InputResponders::Commands.command_list_regex.match(buf)
        line_number = line_number.next
        parser.parse_string(full_buffer)
      end
    rescue Puppet::ParseErrorWithIssue => e
      if multiline_input?(e)
        out_buffer.print '  '
        full_buffer += "\n"
        next
      end
    end
    handle_input(full_buffer)
    full_buffer = ''
  end
end

#responder_listObject



103
104
105
# File 'lib/puppet-debugger/cli.rb', line 103

def responder_list
  plugins = Pluginator.find(PuppetDebugger)
end

#to_resource_declaration(type) ⇒ Object

looks up the type in the catalog by using the type and title and returns the resource in ral format



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/puppet-debugger/cli.rb', line 63

def to_resource_declaration(type)
  if type.respond_to?(:type_name) && type.respond_to?(:title)
    title = type.title
    type_name = type.type_name
  elsif type_result = /(\w+)\['?(\w+)'?\]/.match(type.to_s)
    # not all types have a type_name and title so we
    # output to a string and parse the results
    title = type_result[2]
    type_name = type_result[1]
  else
    return type
  end
  res = scope.catalog.resource(type_name, title)
  return res.to_ral if res
  # don't return anything or returns nil if item is not in the catalog
end