bolt 2.31.0 → 2.35.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +7 -7
  3. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -3
  4. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +17 -6
  5. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +6 -0
  6. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -2
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +24 -6
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -8
  10. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +21 -1
  11. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +18 -1
  12. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +24 -6
  13. data/bolt-modules/out/lib/puppet/functions/out/message.rb +44 -1
  14. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +3 -0
  15. data/guides/logging.txt +18 -0
  16. data/guides/module.txt +19 -0
  17. data/guides/modulepath.txt +25 -0
  18. data/lib/bolt/bolt_option_parser.rb +6 -1
  19. data/lib/bolt/cli.rb +70 -144
  20. data/lib/bolt/config/options.rb +35 -17
  21. data/lib/bolt/config/transport/options.rb +1 -1
  22. data/lib/bolt/error.rb +37 -3
  23. data/lib/bolt/executor.rb +111 -13
  24. data/lib/bolt/inventory/group.rb +2 -1
  25. data/lib/bolt/module_installer.rb +71 -115
  26. data/lib/bolt/{puppetfile → module_installer}/installer.rb +3 -2
  27. data/lib/bolt/module_installer/puppetfile.rb +117 -0
  28. data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
  29. data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
  30. data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
  31. data/lib/bolt/module_installer/resolver.rb +76 -0
  32. data/lib/bolt/module_installer/specs.rb +93 -0
  33. data/lib/bolt/module_installer/specs/forge_spec.rb +85 -0
  34. data/lib/bolt/module_installer/specs/git_spec.rb +179 -0
  35. data/lib/bolt/outputter.rb +0 -47
  36. data/lib/bolt/outputter/human.rb +23 -11
  37. data/lib/bolt/outputter/json.rb +1 -1
  38. data/lib/bolt/pal.rb +48 -30
  39. data/lib/bolt/pal/yaml_plan.rb +11 -2
  40. data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -1
  41. data/lib/bolt/pal/yaml_plan/loader.rb +14 -9
  42. data/lib/bolt/plan_creator.rb +160 -0
  43. data/lib/bolt/plugin.rb +1 -1
  44. data/lib/bolt/project.rb +5 -10
  45. data/lib/bolt/project_migrator/config.rb +2 -1
  46. data/lib/bolt/project_migrator/inventory.rb +2 -2
  47. data/lib/bolt/project_migrator/modules.rb +10 -8
  48. data/lib/bolt/puppetdb/client.rb +3 -2
  49. data/lib/bolt/puppetdb/config.rb +8 -6
  50. data/lib/bolt/result.rb +23 -11
  51. data/lib/bolt/shell/bash.rb +11 -6
  52. data/lib/bolt/shell/powershell.rb +12 -7
  53. data/lib/bolt/task/run.rb +1 -1
  54. data/lib/bolt/transport/base.rb +18 -18
  55. data/lib/bolt/transport/docker.rb +23 -6
  56. data/lib/bolt/transport/orch.rb +23 -19
  57. data/lib/bolt/transport/orch/connection.rb +10 -3
  58. data/lib/bolt/transport/remote.rb +3 -3
  59. data/lib/bolt/transport/simple.rb +6 -6
  60. data/lib/bolt/util.rb +5 -0
  61. data/lib/bolt/version.rb +1 -1
  62. data/lib/bolt/yarn.rb +23 -0
  63. data/lib/bolt_server/file_cache.rb +2 -0
  64. data/lib/bolt_server/schemas/partials/task.json +17 -2
  65. data/lib/bolt_server/transport_app.rb +38 -7
  66. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
  67. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
  68. data/lib/bolt_spec/plans/mock_executor.rb +9 -6
  69. metadata +25 -8
  70. data/lib/bolt/puppetfile.rb +0 -149
  71. data/lib/bolt/puppetfile/module.rb +0 -93
  72. data/modules/secure_env_vars/plans/init.pp +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b9f29b79c8544029c9a69f6dc76b083857a87e178c9aae0359e49c59ce3cfdb
4
- data.tar.gz: 02166a6dda2dc381365b7556f506a419e2717267e88f6015af430f7f725d75d0
3
+ metadata.gz: d05af3f780d500b91be547a73123d24c2da041a8ef60ba14114921358b120800
4
+ data.tar.gz: e8534835e69d1e5e57a353ff89a7d2667cf6d0894e8a07c1e5812282ed85b2cd
5
5
  SHA512:
6
- metadata.gz: 3a282194ca9ccf988e7282afc3a3a0b57b4edc16bb5bf61790fb0eb0ef234e0c7a76c0ea6cd44915047a99e6d423311be1307f435d8000c568d0552268f6afa6
7
- data.tar.gz: 957e6be1a72af03bd448fe070e6f9bb0b78aa1bb65e90a8bda251ef0f6c56e5ffaa8ff64aa0c5852963d9fecb87144b61ffd24494fe0aabcbdff43ecde9947a3
6
+ metadata.gz: 97b3faac5a1e423dcebf8e4fa92f55bbc36bd1c9735fc38332db40a2bb0db0e1ddc8e1e815aa29f31f9eb3601972a8d6e07b39d0c5dc6cd2762ea87a91ddec92
7
+ data.tar.gz: 15963b1bb77ee04a1db4f54996964b32e599e4a0cc422dc77892151b221c1a17df9e6a1c74f160a30e9146402d48819b07b3b9d2ee9c96d7e763bba97389ccc8
data/Puppetfile CHANGED
@@ -6,16 +6,16 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
8
  mod 'puppetlabs-service', '1.3.0'
9
- mod 'puppetlabs-puppet_agent', '4.1.1'
10
- mod 'puppetlabs-facts', '1.1.0'
9
+ mod 'puppetlabs-puppet_agent', '4.2.0'
10
+ mod 'puppetlabs-facts', '1.2.0'
11
11
 
12
12
  # Core types and providers for Puppet 6
13
13
  mod 'puppetlabs-augeas_core', '1.1.1'
14
14
  mod 'puppetlabs-host_core', '1.0.3'
15
15
  mod 'puppetlabs-scheduled_task', '2.2.1'
16
- mod 'puppetlabs-sshkeys_core', '2.1.0'
17
- mod 'puppetlabs-zfs_core', '1.1.0'
18
- mod 'puppetlabs-cron_core', '1.0.4'
16
+ mod 'puppetlabs-sshkeys_core', '2.2.0'
17
+ mod 'puppetlabs-zfs_core', '1.2.0'
18
+ mod 'puppetlabs-cron_core', '1.0.5'
19
19
  mod 'puppetlabs-mount_core', '1.0.4'
20
20
  mod 'puppetlabs-selinux_core', '1.0.4'
21
21
  mod 'puppetlabs-yumrepo_core', '1.0.7'
@@ -34,8 +34,9 @@ mod 'puppetlabs-stdlib', '6.5.0'
34
34
  mod 'puppetlabs-aws_inventory', '0.5.2'
35
35
  mod 'puppetlabs-azure_inventory', '0.4.1'
36
36
  mod 'puppetlabs-gcloud_inventory', '0.1.3'
37
- mod 'puppetlabs-http_request', '0.1.0'
37
+ mod 'puppetlabs-http_request', '0.2.0'
38
38
  mod 'puppetlabs-pkcs7', '0.1.1'
39
+ mod 'puppetlabs-secure_env_vars', '0.1.0'
39
40
  mod 'puppetlabs-terraform', '0.5.0'
40
41
  mod 'puppetlabs-vault', '0.3.0'
41
42
  mod 'puppetlabs-yaml', '0.2.0'
@@ -44,4 +45,3 @@ mod 'puppetlabs-yaml', '0.2.0'
44
45
  mod 'canary', local: true
45
46
  mod 'aggregate', local: true
46
47
  mod 'puppetdb_fact', local: true
47
- mod 'secure_env_vars', local: true
@@ -44,9 +44,7 @@ Puppet::Functions.create_function(:catch_errors) do
44
44
  yield
45
45
  rescue Puppet::PreformattedError => e
46
46
  if e.cause.is_a?(Bolt::Error)
47
- if error_types.nil?
48
- e.cause.to_puppet_error
49
- elsif error_types.include?(e.cause.to_h['kind'])
47
+ if error_types.nil? || error_types.include?(e.cause.to_h['kind'])
50
48
  e.cause.to_puppet_error
51
49
  else
52
50
  raise e
@@ -110,14 +110,25 @@ Puppet::Functions.create_function(:download_file, Puppet::Functions::InternalFun
110
110
  targets = inventory.get_targets(targets)
111
111
  if targets.empty?
112
112
  call_function('debug', "Simulating file download of '#{source}' - no targets given - no action taken")
113
- r = Bolt::ResultSet.new([])
113
+ Bolt::ResultSet.new([])
114
114
  else
115
- r = executor.download_file(targets, source, destination, options)
116
- end
115
+ r = if executor.in_parallel
116
+ require 'concurrent'
117
+ require 'fiber'
118
+ future = Concurrent::Future.execute do
119
+ executor.download_file(targets, source, destination, options, Puppet::Pops::PuppetStack.top_of_stack)
120
+ end
121
+
122
+ Fiber.yield('unfinished') while future.incomplete?
123
+ future.value || future.reason
124
+ else
125
+ executor.download_file(targets, source, destination, options, Puppet::Pops::PuppetStack.top_of_stack)
126
+ end
117
127
 
118
- if !r.ok && !options[:catch_errors]
119
- raise Bolt::RunFailure.new(r, 'download_file', source)
128
+ if !r.ok && !options[:catch_errors]
129
+ raise Bolt::RunFailure.new(r, 'download_file', source)
130
+ end
131
+ r
120
132
  end
121
- r
122
133
  end
123
134
  end
@@ -3,6 +3,12 @@
3
3
  require 'bolt/error'
4
4
 
5
5
  # Returns the facts hash for a target.
6
+ #
7
+ # Using the `facts` function does not automatically collect facts for a target,
8
+ # and will only return facts that are currently set in the inventory. To collect
9
+ # facts from a target and set them in the inventory, run the
10
+ # [facts](writing_plans.md#collect-facts-from-targets) plan or
11
+ # [puppetdb_fact](writing_plans.md#collect-facts-from-puppetdb) plan.
6
12
  Puppet::Functions.create_function(:facts) do
7
13
  # @param target A target.
8
14
  # @return The target's facts.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/yarn'
4
+
5
+ # Map a code block onto an array, where each array element executes in parallel.
6
+ # This function is experimental.
7
+ #
8
+ # > **Note:** Not available in apply block.
9
+ Puppet::Functions.create_function(:parallelize, Puppet::Functions::InternalFunction) do
10
+ # Map a block onto an array, where each array element executes in parallel.
11
+ # This function is experimental.
12
+ # @param data The array to apply the block to.
13
+ # @return [Array] An array of PlanResult objects. Each input from the input
14
+ # array returns a corresponding PlanResult object.
15
+ # @example Execute two tasks on multiple targets. Once the task finishes on one
16
+ # target, that target can move to the next step without waiting for the task
17
+ # to finish on the second target.
18
+ # $targets = get_targets(["host1", "host2"])
19
+ # $result = parallelize ($targets) |$t| {
20
+ # run_task('a', $t)
21
+ # run_task('b', $t)
22
+ # }
23
+ dispatch :parallelize do
24
+ scope_param
25
+ param 'Array[Any]', :data
26
+ block_param 'Callable[Any]', :block
27
+ return_type 'Array[Boltlib::PlanResult]'
28
+ end
29
+
30
+ def parallelize(scope, data, &block)
31
+ unless Puppet[:tasks]
32
+ raise Puppet::ParseErrorWithIssue
33
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'parallelize')
34
+ end
35
+
36
+ executor = Puppet.lookup(:bolt_executor)
37
+ executor.report_function_call(self.class.name)
38
+
39
+ skein = data.each_with_index.map do |object, index|
40
+ executor.create_yarn(scope, block, object, index)
41
+ end
42
+
43
+ result = executor.round_robin(skein)
44
+
45
+ failed_indices = result.each_index.select do |i|
46
+ result[i].is_a?(Bolt::Error)
47
+ end
48
+
49
+ # TODO: Inner catch errors block?
50
+ if failed_indices.any?
51
+ raise Bolt::ParallelFailure.new(result, failed_indices)
52
+ end
53
+
54
+ result
55
+ end
56
+ end
@@ -2,12 +2,12 @@
2
2
 
3
3
  require 'bolt/error'
4
4
 
5
- # Makes a query to {https://puppet.com/docs/puppetdb/latest/index.html puppetdb}
5
+ # Makes a query to [puppetdb](https://puppet.com/docs/puppetdb/latest/index.html)
6
6
  # using Bolt's PuppetDB client.
7
7
  Puppet::Functions.create_function(:puppetdb_query) do
8
8
  # rubocop:disable Layout/LineLength
9
9
  # @param query A PQL query.
10
- # {https://puppet.com/docs/puppetdb/latest/api/query/tutorial-pql.html Learn more about Puppet's query language, PQL}
10
+ # Learn more about [Puppet's query language](https://puppet.com/docs/puppetdb/latest/api/query/tutorial-pql.html), PQL.
11
11
  # @return Results of the PuppetDB query.
12
12
  # @example Request certnames for all nodes
13
13
  # puppetdb_query('nodes[certname] {}')
@@ -67,14 +67,32 @@ Puppet::Functions.create_function(:run_command) do
67
67
 
68
68
  if targets.empty?
69
69
  call_function('debug', "Simulating run_command('#{command}') - no targets given - no action taken")
70
- r = Bolt::ResultSet.new([])
70
+ Bolt::ResultSet.new([])
71
71
  else
72
- r = executor.run_command(targets, command, options)
73
- end
72
+ r = if executor.in_parallel
73
+ require 'concurrent'
74
+ require 'fiber'
75
+ future = Concurrent::Future.execute do
76
+ executor.run_command(targets,
77
+ command,
78
+ options,
79
+ Puppet::Pops::PuppetStack.top_of_stack)
80
+ end
81
+
82
+ Fiber.yield('unfinished') while future.incomplete?
83
+ future.value || future.reason
84
+ else
85
+ executor.run_command(targets,
86
+ command,
87
+ options,
88
+ Puppet::Pops::PuppetStack.top_of_stack)
89
+ end
90
+
91
+ if !r.ok && !options[:catch_errors]
92
+ raise Bolt::RunFailure.new(r, 'run_command', command)
93
+ end
74
94
 
75
- if !r.ok && !options[:catch_errors]
76
- raise Bolt::RunFailure.new(r, 'run_command', command)
95
+ r
77
96
  end
78
- r
79
97
  end
80
98
  end
@@ -84,15 +84,34 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
84
84
  # Ensure that given targets are all Target instances)
85
85
  targets = inventory.get_targets(targets)
86
86
 
87
- r = if targets.empty?
88
- Bolt::ResultSet.new([])
89
- else
90
- executor.run_script(targets, found, arguments, options)
91
- end
87
+ if targets.empty?
88
+ Bolt::ResultSet.new([])
89
+ else
90
+ r = if executor.in_parallel
91
+ require 'concurrent'
92
+ require 'fiber'
93
+ future = Concurrent::Future.execute do
94
+ executor.run_script(targets,
95
+ found,
96
+ arguments,
97
+ options,
98
+ Puppet::Pops::PuppetStack.top_of_stack)
99
+ end
92
100
 
93
- if !r.ok && !options[:catch_errors]
94
- raise Bolt::RunFailure.new(r, 'run_script', script)
101
+ Fiber.yield('unfinished') while future.incomplete?
102
+ future.value || future.reason
103
+ else
104
+ executor.run_script(targets,
105
+ found,
106
+ arguments,
107
+ options,
108
+ Puppet::Pops::PuppetStack.top_of_stack)
109
+ end
110
+
111
+ if !r.ok && !options[:catch_errors]
112
+ raise Bolt::RunFailure.new(r, 'run_script', script)
113
+ end
114
+ r
95
115
  end
96
- r
97
116
  end
98
117
  end
@@ -133,7 +133,27 @@ Puppet::Functions.create_function(:run_task) do
133
133
  if targets.empty?
134
134
  Bolt::ResultSet.new([])
135
135
  else
136
- result = executor.run_task(targets, task, params, options)
136
+ result = if executor.in_parallel
137
+ require 'concurrent'
138
+ require 'fiber'
139
+ future = Concurrent::Future.execute do
140
+ executor.run_task(targets,
141
+ task,
142
+ params,
143
+ options,
144
+ Puppet::Pops::PuppetStack.top_of_stack)
145
+ end
146
+
147
+ Fiber.yield('unfinished') while future.incomplete?
148
+ future.value || future.reason
149
+ else
150
+ executor.run_task(targets,
151
+ task,
152
+ params,
153
+ options,
154
+ Puppet::Pops::PuppetStack.top_of_stack)
155
+ end
156
+
137
157
  if !result.ok && !options[:catch_errors]
138
158
  raise Bolt::RunFailure.new(result, 'run_task', task_name)
139
159
  end
@@ -180,7 +180,24 @@ Puppet::Functions.create_function(:run_task_with) do
180
180
  else
181
181
  # Combine the results from the task run with any failing results that were
182
182
  # generated earlier when creating the target mapping
183
- task_result = executor.run_task_with(target_mapping, task, options)
183
+ task_result = if executor.in_parallel
184
+ require 'concurrent'
185
+ require 'fiber'
186
+ future = Concurrent::Future.execute do
187
+ executor.run_task_with(target_mapping,
188
+ task,
189
+ options,
190
+ Puppet::Pops::PuppetStack.top_of_stack)
191
+ end
192
+
193
+ Fiber.yield('unfinished') while future.incomplete?
194
+ future.value || future.reason
195
+ else
196
+ executor.run_task_with(target_mapping,
197
+ task,
198
+ options,
199
+ Puppet::Pops::PuppetStack.top_of_stack)
200
+ end
184
201
  result = Bolt::ResultSet.new(task_result.results + error_set)
185
202
 
186
203
  if !result.ok && !options[:catch_errors]
@@ -81,14 +81,32 @@ Puppet::Functions.create_function(:upload_file, Puppet::Functions::InternalFunct
81
81
  targets = inventory.get_targets(targets)
82
82
  if targets.empty?
83
83
  call_function('debug', "Simulating file upload of '#{found}' - no targets given - no action taken")
84
- r = Bolt::ResultSet.new([])
84
+ Bolt::ResultSet.new([])
85
85
  else
86
- r = executor.upload_file(targets, found, destination, options)
87
- end
86
+ r = if executor.in_parallel
87
+ require 'concurrent'
88
+ require 'fiber'
89
+ future = Concurrent::Future.execute do
90
+ executor.upload_file(targets,
91
+ found,
92
+ destination,
93
+ options,
94
+ Puppet::Pops::PuppetStack.top_of_stack)
95
+ end
88
96
 
89
- if !r.ok && !options[:catch_errors]
90
- raise Bolt::RunFailure.new(r, 'upload_file', source)
97
+ Fiber.yield('unfinished') while future.incomplete?
98
+ future.value || future.reason
99
+ else
100
+ executor.upload_file(targets,
101
+ found,
102
+ destination,
103
+ options,
104
+ Puppet::Pops::PuppetStack.top_of_stack)
105
+ end
106
+ if !r.ok && !options[:catch_errors]
107
+ raise Bolt::RunFailure.new(r, 'upload_file', source)
108
+ end
109
+ r
91
110
  end
92
- r
93
111
  end
94
112
  end
@@ -26,8 +26,51 @@ Puppet::Functions.create_function(:'out::message') do
26
26
  # Send Analytics Report
27
27
  executor.report_function_call(self.class.name)
28
28
 
29
- executor.publish_event(type: :message, message: message)
29
+ executor.publish_event(type: :message, message: stringify(message))
30
30
 
31
31
  nil
32
32
  end
33
+
34
+ def stringify(message)
35
+ formatted = format_message(message)
36
+ if formatted.is_a?(Hash) || formatted.is_a?(Array)
37
+ ::JSON.pretty_generate(formatted)
38
+ else
39
+ formatted
40
+ end
41
+ end
42
+
43
+ def format_message(message)
44
+ case message
45
+ when Array
46
+ message.map { |item| format_message(item) }
47
+ when Bolt::ApplyResult
48
+ format_apply_result(message)
49
+ when Bolt::Result, Bolt::ResultSet
50
+ # This is equivalent to to_s, but formattable
51
+ message.to_data
52
+ when Bolt::RunFailure
53
+ formatted_resultset = message.result_set.to_data
54
+ message.to_h.merge('result_set' => formatted_resultset)
55
+ when Hash
56
+ message.each_with_object({}) do |(k, v), h|
57
+ h[format_message(k)] = format_message(v)
58
+ end
59
+ when Integer, Float, NilClass
60
+ message
61
+ else
62
+ message.to_s
63
+ end
64
+ end
65
+
66
+ def format_apply_result(result)
67
+ logs = result.resource_logs&.map do |log|
68
+ # Omit low-level info/debug messages
69
+ next if %w[info debug].include?(log['level'])
70
+ indent(2, format_log(log))
71
+ end
72
+ hash = result.to_data
73
+ hash['logs'] = logs unless logs.empty?
74
+ hash
75
+ end
33
76
  end
@@ -9,11 +9,14 @@ Puppet::Functions.create_function(:prompt) do
9
9
  # @param prompt The prompt to display.
10
10
  # @param options A hash of additional options.
11
11
  # @option options [Boolean] sensitive Disable echo back and mark the response as sensitive.
12
+ # The returned value will be wrapped by the `Sensitive` data type. To access the raw
13
+ # value, use the `unwrap` function (i.e. `$sensitive_value.unwrap`).
12
14
  # @return The response to the prompt.
13
15
  # @example Prompt the user if plan execution should continue
14
16
  # $response = prompt('Continue executing plan? [Y\N]')
15
17
  # @example Prompt the user for sensitive information
16
18
  # $password = prompt('Enter your password', 'sensitive' => true)
19
+ # out::message("Password is: ${password.unwrap}")
17
20
  dispatch :prompt do
18
21
  param 'String', :prompt
19
22
  optional_param 'Hash[String[1], Any]', :options
@@ -0,0 +1,18 @@
1
+ TOPIC
2
+ logging
3
+
4
+ DESCRIPTION
5
+ Bolt prints messages both to the console and to log files. Messages can
6
+ either come from Bolt's 'outputter', which logs user-facing messages like
7
+ progress and results, or from the 'logger', which logs warnings, errors, and
8
+ log-structured output to log files. Both of these message streams are
9
+ configurable.
10
+
11
+ By default, Bolt logs to the console at 'warn' level and writes a log file to
12
+ '<project>/bolt-debug.log' at 'debug' level. Unless you are running a plan,
13
+ Bolt runs in verbose mode by default.
14
+
15
+ To learn more about projects, see the 'project' guide.
16
+
17
+ DOCUMENTATION
18
+ https://pup.pt/bolt-logging