bolt 2.19.0 → 2.24.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +3 -1
  3. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +123 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +6 -0
  5. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +12 -6
  6. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
  7. data/bolt-modules/out/lib/puppet/functions/out/message.rb +1 -1
  8. data/exe/bolt +1 -0
  9. data/guides/inventory.txt +19 -0
  10. data/guides/project.txt +22 -0
  11. data/lib/bolt/analytics.rb +5 -5
  12. data/lib/bolt/applicator.rb +4 -3
  13. data/lib/bolt/bolt_option_parser.rb +100 -27
  14. data/lib/bolt/catalog.rb +12 -3
  15. data/lib/bolt/cli.rb +356 -156
  16. data/lib/bolt/config.rb +2 -2
  17. data/lib/bolt/config/options.rb +18 -4
  18. data/lib/bolt/executor.rb +30 -7
  19. data/lib/bolt/inventory/group.rb +6 -5
  20. data/lib/bolt/inventory/inventory.rb +4 -3
  21. data/lib/bolt/logger.rb +3 -4
  22. data/lib/bolt/module.rb +2 -1
  23. data/lib/bolt/outputter.rb +56 -0
  24. data/lib/bolt/outputter/human.rb +10 -9
  25. data/lib/bolt/outputter/json.rb +11 -4
  26. data/lib/bolt/outputter/logger.rb +2 -2
  27. data/lib/bolt/outputter/rainbow.rb +18 -2
  28. data/lib/bolt/pal.rb +13 -11
  29. data/lib/bolt/pal/yaml_plan/evaluator.rb +22 -1
  30. data/lib/bolt/pal/yaml_plan/step.rb +24 -2
  31. data/lib/bolt/pal/yaml_plan/step/download.rb +38 -0
  32. data/lib/bolt/pal/yaml_plan/step/message.rb +30 -0
  33. data/lib/bolt/pal/yaml_plan/step/upload.rb +3 -3
  34. data/lib/bolt/pal/yaml_plan/transpiler.rb +11 -3
  35. data/lib/bolt/plugin/prompt.rb +3 -3
  36. data/lib/bolt/plugin/puppetdb.rb +3 -2
  37. data/lib/bolt/project.rb +7 -4
  38. data/lib/bolt/project_migrate.rb +138 -0
  39. data/lib/bolt/puppetdb/client.rb +2 -0
  40. data/lib/bolt/puppetdb/config.rb +16 -0
  41. data/lib/bolt/result.rb +7 -0
  42. data/lib/bolt/shell/bash.rb +31 -11
  43. data/lib/bolt/shell/powershell.rb +10 -4
  44. data/lib/bolt/transport/base.rb +24 -0
  45. data/lib/bolt/transport/docker.rb +8 -0
  46. data/lib/bolt/transport/docker/connection.rb +28 -10
  47. data/lib/bolt/transport/local/connection.rb +15 -2
  48. data/lib/bolt/transport/orch.rb +15 -3
  49. data/lib/bolt/transport/simple.rb +6 -0
  50. data/lib/bolt/transport/ssh/connection.rb +13 -5
  51. data/lib/bolt/transport/ssh/exec_connection.rb +24 -3
  52. data/lib/bolt/transport/winrm/connection.rb +125 -15
  53. data/lib/bolt/util.rb +27 -12
  54. data/lib/bolt/util/puppet_log_level.rb +4 -3
  55. data/lib/bolt/version.rb +1 -1
  56. data/lib/bolt_server/base_config.rb +1 -1
  57. data/lib/bolt_server/pe/pal.rb +1 -1
  58. data/lib/bolt_server/transport_app.rb +79 -2
  59. data/lib/bolt_spec/bolt_context.rb +7 -2
  60. data/lib/bolt_spec/plans.rb +16 -3
  61. data/lib/bolt_spec/plans/action_stubs.rb +3 -2
  62. data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
  63. data/lib/bolt_spec/plans/mock_executor.rb +14 -1
  64. data/lib/bolt_spec/run.rb +22 -0
  65. data/libexec/apply_catalog.rb +2 -2
  66. data/libexec/bolt_catalog +4 -3
  67. data/libexec/custom_facts.rb +1 -1
  68. data/libexec/query_resources.rb +1 -1
  69. data/modules/secure_env_vars/plans/init.pp +20 -0
  70. metadata +11 -2
@@ -204,7 +204,7 @@ module Bolt
204
204
  'compile-concurrency' => Etc.nprocessors,
205
205
  'concurrency' => default_concurrency,
206
206
  'format' => 'human',
207
- 'log' => { 'console' => {} },
207
+ 'log' => { 'console' => {}, 'bolt-debug.log' => { 'level' => 'debug', 'append' => false } },
208
208
  'plugin_hooks' => {},
209
209
  'plugins' => {},
210
210
  'puppetdb' => {},
@@ -499,7 +499,7 @@ module Bolt
499
499
  end
500
500
 
501
501
  def matching_paths(paths)
502
- [*paths].map { |p| Dir.glob([p, casefold(p)]) }.flatten.uniq.reject { |p| [*paths].include?(p) }
502
+ Array(paths).map { |p| Dir.glob([p, casefold(p)]) }.flatten.uniq.reject { |p| Array(paths).include?(p) }
503
503
  end
504
504
 
505
505
  private def casefold(path)
@@ -186,8 +186,8 @@ module Bolt
186
186
  "level" => {
187
187
  description: "The type of information to log.",
188
188
  type: String,
189
- enum: %w[debug error info notice warn fatal any],
190
- _default: "warn for console, notice for file"
189
+ enum: %w[trace debug error info warn fatal any],
190
+ _default: "warn"
191
191
  }
192
192
  }
193
193
  }
@@ -204,8 +204,8 @@ module Bolt
204
204
  "level" => {
205
205
  description: "The type of information to log.",
206
206
  type: String,
207
- enum: %w[debug error info notice warn fatal any],
208
- _default: "warn for console, notice for file"
207
+ enum: %w[trace debug error info warn fatal any],
208
+ _default: "warn"
209
209
  }
210
210
  }
211
211
  },
@@ -275,11 +275,25 @@ module Bolt
275
275
  type: String,
276
276
  _example: "/etc/puppetlabs/puppet/ssl/certs/my-host.example.com.pem"
277
277
  },
278
+ "connect_timeout" => {
279
+ description: "How long to wait in seconds when establishing connections with PuppetDB.",
280
+ type: Integer,
281
+ minimum: 1,
282
+ _default: 60,
283
+ _example: 120
284
+ },
278
285
  "key" => {
279
286
  description: "The private key for the certificate.",
280
287
  type: String,
281
288
  _example: "/etc/puppetlabs/puppet/ssl/private_keys/my-host.example.com.pem"
282
289
  },
290
+ "read_timeout" => {
291
+ description: "How long to wait in seconds for a response from PuppetDB.",
292
+ type: Integer,
293
+ minimum: 1,
294
+ _default: 60,
295
+ _example: 120
296
+ },
283
297
  "server_urls" => {
284
298
  description: "An array containing the PuppetDB host to connect to. Include the protocol `https` "\
285
299
  "and the port, which is usually `8081`. For example, "\
@@ -56,11 +56,12 @@ module Bolt
56
56
  @reported_transports = Set.new
57
57
  @subscribers = {}
58
58
  @publisher = Concurrent::SingleThreadExecutor.new
59
+ @publisher.post { Thread.current[:name] = 'event-publisher' }
59
60
 
60
61
  @noop = noop
61
62
  @run_as = nil
62
63
  @pool = if concurrency > 0
63
- Concurrent::ThreadPoolExecutor.new(max_threads: concurrency)
64
+ Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
64
65
  else
65
66
  Concurrent.global_immediate_executor
66
67
  end
@@ -125,6 +126,7 @@ module Bolt
125
126
  # Pass this argument through to avoid retaining a reference to a
126
127
  # local variable that will change on the next iteration of the loop.
127
128
  @pool.post(batch_promises) do |result_promises|
129
+ Thread.current[:name] ||= Thread.current.name
128
130
  results = yield transport, batch
129
131
  Array(results).each do |result|
130
132
  result_promises[result.target].set(result)
@@ -241,7 +243,7 @@ module Bolt
241
243
 
242
244
  @analytics&.event('Plan', 'yaml', plan_steps: steps, return_type: return_type)
243
245
  rescue StandardError => e
244
- @logger.debug { "Failed to submit analytics event: #{e.message}" }
246
+ @logger.trace { "Failed to submit analytics event: #{e.message}" }
245
247
  end
246
248
 
247
249
  def with_node_logging(description, batch)
@@ -320,6 +322,27 @@ module Bolt
320
322
  end
321
323
  end
322
324
 
325
+ def download_file(targets, source, destination, options = {})
326
+ description = options.fetch(:description, "file download from #{source} to #{destination}")
327
+
328
+ begin
329
+ FileUtils.mkdir_p(destination)
330
+ rescue Errno::EEXIST => e
331
+ message = "#{e.message}; unable to create destination directory #{destination}"
332
+ raise Bolt::Error.new(message, 'bolt/file-exist-error')
333
+ end
334
+
335
+ log_action(description, targets) do
336
+ options[:run_as] = run_as if run_as && !options.key?(:run_as)
337
+
338
+ batch_execute(targets) do |transport, batch|
339
+ with_node_logging("Downloading file #{source} to #{destination}", batch) do
340
+ transport.batch_download(batch, source, destination, options, &method(:publish_event))
341
+ end
342
+ end
343
+ end
344
+ end
345
+
323
346
  def run_plan(scope, plan, params)
324
347
  plan.call_by_name_with_scope(scope, params, true)
325
348
  end
@@ -360,19 +383,19 @@ module Bolt
360
383
  end
361
384
 
362
385
  def prompt(prompt, options)
363
- unless STDIN.tty?
386
+ unless $stdin.tty?
364
387
  raise Bolt::Error.new('STDIN is not a tty, unable to prompt', 'bolt/no-tty-error')
365
388
  end
366
389
 
367
- STDERR.print("#{prompt}: ")
390
+ $stderr.print("#{prompt}: ")
368
391
 
369
392
  value = if options[:sensitive]
370
- STDIN.noecho(&:gets).to_s.chomp
393
+ $stdin.noecho(&:gets).to_s.chomp
371
394
  else
372
- STDIN.gets.to_s.chomp
395
+ $stdin.gets.to_s.chomp
373
396
  end
374
397
 
375
- STDERR.puts if options[:sensitive]
398
+ $stderr.puts if options[:sensitive]
376
399
 
377
400
  value
378
401
  end
@@ -50,10 +50,11 @@ module Bolt
50
50
  # or it could be a name/alias of a target defined in another group.
51
51
  # We can't tell the difference until all groups have been resolved,
52
52
  # so we store the string on its own here and process it later.
53
- if target.is_a?(String)
53
+ case target
54
+ when String
54
55
  @string_targets << target
55
56
  # Handle plugins at this level so that lookups cannot trigger recursive lookups
56
- elsif target.is_a?(Hash)
57
+ when Hash
57
58
  add_target_definition(target)
58
59
  else
59
60
  raise ValidationError.new("Target entry must be a String or Hash, not #{target.class}", @name)
@@ -118,7 +119,7 @@ module Bolt
118
119
  end
119
120
 
120
121
  if contains_target?(t_name)
121
- @logger.warn("Ignoring duplicate target in #{@name}: #{target}")
122
+ @logger.debug("Ignoring duplicate target in #{@name}: #{target}")
122
123
  return
123
124
  end
124
125
 
@@ -199,14 +200,14 @@ module Bolt
199
200
  # If this is an alias for an existing target, then add it to this group
200
201
  elsif (canonical_name = aliases[string_target])
201
202
  if contains_target?(canonical_name)
202
- @logger.warn("Ignoring duplicate target in #{@name}: #{canonical_name}")
203
+ @logger.debug("Ignoring duplicate target in #{@name}: #{canonical_name}")
203
204
  else
204
205
  @unresolved_targets[canonical_name] = { 'name' => canonical_name }
205
206
  end
206
207
  # If it's not the name or alias of an existing target, then make a
207
208
  # new target using the string as the URI
208
209
  elsif contains_target?(string_target)
209
- @logger.warn("Ignoring duplicate target in #{@name}: #{string_target}")
210
+ @logger.debug("Ignoring duplicate target in #{@name}: #{string_target}")
210
211
  else
211
212
  @unresolved_targets[string_target] = { 'uri' => string_target }
212
213
  end
@@ -109,11 +109,12 @@ module Bolt
109
109
  private :resolve_name
110
110
 
111
111
  def expand_targets(targets)
112
- if targets.is_a? Bolt::Target
112
+ case targets
113
+ when Bolt::Target
113
114
  targets
114
- elsif targets.is_a? Array
115
+ when Array
115
116
  targets.map { |tish| expand_targets(tish) }
116
- elsif targets.is_a? String
117
+ when String
117
118
  # Expand a comma-separated list
118
119
  targets.split(/[[:space:],]+/).reject(&:empty?).map do |name|
119
120
  ts = resolve_name(name)
@@ -14,13 +14,12 @@ module Bolt
14
14
  # redefs, so skip it if it's already been initialized
15
15
  return if Logging.initialized?
16
16
 
17
- Logging.init :debug, :info, :notice, :warn, :error, :fatal, :any
17
+ Logging.init :trace, :debug, :info, :notice, :warn, :error, :fatal, :any
18
18
  @mutex = Mutex.new
19
19
 
20
20
  Logging.color_scheme(
21
21
  'bolt',
22
22
  lines: {
23
- notice: :green,
24
23
  warn: :yellow,
25
24
  error: :red,
26
25
  fatal: %i[white on_red]
@@ -81,7 +80,7 @@ module Bolt
81
80
 
82
81
  def self.default_layout
83
82
  Logging.layouts.pattern(
84
- pattern: '%d %-6l %c: %m\n',
83
+ pattern: '%d %-6l [%T] [%c] %m\n',
85
84
  date_pattern: '%Y-%m-%dT%H:%M:%S.%6N'
86
85
  )
87
86
  end
@@ -91,7 +90,7 @@ module Bolt
91
90
  end
92
91
 
93
92
  def self.default_file_level
94
- :notice
93
+ :warn
95
94
  end
96
95
 
97
96
  # Explicitly check the log level names instead of the log level number, as levels
@@ -3,7 +3,8 @@
3
3
  # Is this Bolt::Pobs::Module?
4
4
  module Bolt
5
5
  class Module
6
- MODULE_NAME_REGEX = /\A[a-z][a-z0-9_]*\z/.freeze
6
+ CONTENT_NAME_REGEX = /\A[a-z][a-z0-9_]*(::[a-z][a-z0-9_]*)*\z/.freeze
7
+ MODULE_NAME_REGEX = /\A[a-z][a-z0-9_]*\z/.freeze
7
8
 
8
9
  def self.discover(modulepath)
9
10
  modulepath.each_with_object({}) do |path, mods|
@@ -21,6 +21,62 @@ module Bolt
21
21
  @trace = trace
22
22
  @stream = stream
23
23
  end
24
+
25
+ def indent(indent, string)
26
+ indent = ' ' * indent
27
+ string.gsub(/^/, indent.to_s)
28
+ end
29
+
30
+ def print_message_event(event)
31
+ print_message(stringify(event[:message]))
32
+ end
33
+
34
+ def print_message
35
+ raise NotImplementedError, "print_message() must be implemented by the outputter class"
36
+ end
37
+
38
+ def stringify(message)
39
+ formatted = format_message(message)
40
+ if formatted.is_a?(Hash) || formatted.is_a?(Array)
41
+ ::JSON.pretty_generate(formatted)
42
+ else
43
+ formatted
44
+ end
45
+ end
46
+
47
+ def format_message(message)
48
+ case message
49
+ when Array
50
+ message.map { |item| format_message(item) }
51
+ when Bolt::ApplyResult
52
+ format_apply_result(message)
53
+ when Bolt::Result, Bolt::ResultSet
54
+ # This is equivalent to to_s, but formattable
55
+ message.to_data
56
+ when Bolt::RunFailure
57
+ formatted_resultset = message.result_set.to_data
58
+ message.to_h.merge('result_set' => formatted_resultset)
59
+ when Hash
60
+ message.each_with_object({}) do |(k, v), h|
61
+ h[format_message(k)] = format_message(v)
62
+ end
63
+ when Integer, Float, NilClass
64
+ message
65
+ else
66
+ message.to_s
67
+ end
68
+ end
69
+
70
+ def format_apply_result(result)
71
+ logs = result.resource_logs&.map do |log|
72
+ # Omit low-level info/debug messages
73
+ next if %w[info debug].include?(log['level'])
74
+ indent(2, format_log(log))
75
+ end
76
+ hash = result.to_data
77
+ hash['logs'] = logs unless logs.empty?
78
+ hash
79
+ end
24
80
  end
25
81
  end
26
82
 
@@ -27,11 +27,6 @@ module Bolt
27
27
  end
28
28
  end
29
29
 
30
- def indent(indent, string)
31
- indent = ' ' * indent
32
- string.gsub(/^/, indent.to_s)
33
- end
34
-
35
30
  def remove_trail(string)
36
31
  string.sub(/\s\z/, '')
37
32
  end
@@ -291,6 +286,16 @@ module Bolt
291
286
  "details and parameters for a specific plan.")
292
287
  end
293
288
 
289
+ def print_topics(topics)
290
+ print_message("Available topics are:")
291
+ print_message(topics.join("\n"))
292
+ print_message("\nUse `bolt guide <topic>` to view a specific guide.")
293
+ end
294
+
295
+ def print_guide(guide, _topic)
296
+ @stream.puts(guide)
297
+ end
298
+
294
299
  def print_module_list(module_list)
295
300
  module_list.each do |path, modules|
296
301
  if (mod = modules.find { |m| m[:internal_module_group] })
@@ -372,10 +377,6 @@ module Bolt
372
377
  end
373
378
  end
374
379
 
375
- def print_message_event(event)
376
- print_message(event[:message])
377
- end
378
-
379
380
  def fatal_error(err)
380
381
  @stream.puts(colorize(:red, err.message))
381
382
  if err.is_a? Bolt::RunFailure
@@ -83,6 +83,17 @@ module Bolt
83
83
  @stream.puts result.to_json
84
84
  end
85
85
 
86
+ def print_topics(topics)
87
+ print_table('topics' => topics)
88
+ end
89
+
90
+ def print_guide(guide, topic)
91
+ @stream.puts({
92
+ 'topic' => topic,
93
+ 'guide' => guide
94
+ }.to_json)
95
+ end
96
+
86
97
  def print_puppetfile_result(success, puppetfile, moduledir)
87
98
  @stream.puts({ "success": success,
88
99
  "puppetfile": puppetfile,
@@ -121,10 +132,6 @@ module Bolt
121
132
  @stream.puts '}' if @object_open
122
133
  end
123
134
 
124
- def print_message_event(event)
125
- print_message(event[:message])
126
- end
127
-
128
135
  def print_message(message)
129
136
  $stderr.puts(message)
130
137
  end
@@ -40,13 +40,13 @@ module Bolt
40
40
 
41
41
  def log_plan_start(event)
42
42
  plan = event[:plan]
43
- @logger.notice("Starting: plan #{plan}")
43
+ @logger.info("Starting: plan #{plan}")
44
44
  end
45
45
 
46
46
  def log_plan_finish(event)
47
47
  plan = event[:plan]
48
48
  duration = event[:duration]
49
- @logger.notice("Finished: plan #{plan} in #{duration.round(2)} sec")
49
+ @logger.info("Finished: plan #{plan} in #{duration.round(2)} sec")
50
50
  end
51
51
  end
52
52
  end
@@ -39,9 +39,10 @@ module Bolt
39
39
  a = string.chars.map do |c|
40
40
  case @state
41
41
  when :normal
42
- if c == "\e"
42
+ case c
43
+ when "\e"
43
44
  @state = :ansi
44
- elsif c == "\n"
45
+ when "\n"
45
46
  @line_color += 1
46
47
  @color = @line_color
47
48
  c
@@ -85,6 +86,21 @@ module Bolt
85
86
  total_msg << " in #{duration_to_string(elapsed_time)}" unless elapsed_time.nil?
86
87
  @stream.puts colorize(:rainbow, total_msg)
87
88
  end
89
+
90
+ def print_guide(guide, _topic)
91
+ @stream.puts colorize(:rainbow, guide)
92
+ end
93
+
94
+ def print_topics(topics)
95
+ content = String.new("Available topics are:\n")
96
+ content += topics.join("\n")
97
+ content += "\n\nUse `bolt guide <topic>` to view a specific guide."
98
+ @stream.puts colorize(:rainbow, content)
99
+ end
100
+
101
+ def print_message(message)
102
+ @stream.puts colorize(:rainbow, message)
103
+ end
88
104
  end
89
105
  end
90
106
  end
@@ -48,7 +48,7 @@ module Bolt
48
48
  end
49
49
  end
50
50
 
51
- attr_reader :modulepath
51
+ attr_reader :modulepath, :user_modulepath
52
52
 
53
53
  def initialize(modulepath, hiera_config, resource_types, max_compiles = Etc.nprocessors,
54
54
  trusted_external = nil, apply_settings = {}, project = nil)
@@ -56,7 +56,7 @@ module Bolt
56
56
  # is safe and in practice only happens in tests
57
57
  self.class.load_puppet
58
58
 
59
- @original_modulepath = modulepath
59
+ @user_modulepath = modulepath
60
60
  @modulepath = [BOLTLIB_PATH, *modulepath, MODULES_PATH]
61
61
  @hiera_config = hiera_config
62
62
  @trusted_external = trusted_external
@@ -67,7 +67,7 @@ module Bolt
67
67
 
68
68
  @logger = Logging.logger[self]
69
69
  if modulepath && !modulepath.empty?
70
- @logger.info("Loading modules from #{@modulepath.join(File::PATH_SEPARATOR)}")
70
+ @logger.debug("Loading modules from #{@modulepath.join(File::PATH_SEPARATOR)}")
71
71
  end
72
72
 
73
73
  @loaded = false
@@ -150,7 +150,12 @@ module Bolt
150
150
  r = Puppet::Pal.in_tmp_environment('bolt', modulepath: @modulepath, facts: {}) do |pal|
151
151
  # Only load the project if it a) exists, b) has a name it can be loaded with
152
152
  bolt_project = @project if @project&.name
153
+ # Puppet currently won't receive the project unless it is a named project. Since
154
+ # the download_file plan function needs access to the project path, add it to the
155
+ # context.
156
+ bolt_project_data = @project
153
157
  Puppet.override(bolt_project: bolt_project,
158
+ bolt_project_data: bolt_project_data,
154
159
  yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
155
160
  pal.with_script_compiler do |compiler|
156
161
  alias_types(compiler)
@@ -203,7 +208,7 @@ module Bolt
203
208
  # Skip syncing built-in plugins, since we vendor some Puppet 6
204
209
  # versions of "core" types, which are already present on the agent,
205
210
  # but may cause issues on Puppet 5 agents.
206
- @original_modulepath,
211
+ @user_modulepath,
207
212
  @project,
208
213
  pdb_client,
209
214
  @hiera_config,
@@ -273,15 +278,12 @@ module Bolt
273
278
  end
274
279
  end
275
280
 
276
- def list_modulepath
277
- @modulepath - [BOLTLIB_PATH, MODULES_PATH]
278
- end
279
-
280
281
  def parse_params(type, object_name, params)
281
282
  in_bolt_compiler do |compiler|
282
- if type == 'task'
283
+ case type
284
+ when 'task'
283
285
  param_spec = compiler.task_signature(object_name)&.task_hash&.dig('parameters')
284
- elsif type == 'plan'
286
+ when 'plan'
285
287
  plan = compiler.plan_signature(object_name)
286
288
  param_spec = plan.params_type.elements&.each_with_object({}) { |t, h| h[t.name] = t.value_type } if plan
287
289
  end
@@ -407,7 +409,7 @@ module Bolt
407
409
  end
408
410
  params[name] = { 'type' => type_str }
409
411
  params[name]['sensitive'] = param.type_expr.instance_of?(Puppet::Pops::Types::PSensitiveType)
410
- params[name]['default_value'] = param.value
412
+ params[name]['default_value'] = param.value unless param.value.nil?
411
413
  params[name]['description'] = param.description if param.description
412
414
  end
413
415
  {