bolt 2.11.0 → 2.15.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +3 -2
  4. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -1
  5. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -1
  6. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +52 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +65 -0
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +4 -2
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +8 -2
  10. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +65 -43
  11. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +1 -1
  12. data/bolt-modules/file/lib/puppet/functions/file/read.rb +1 -1
  13. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +1 -1
  14. data/lib/bolt/analytics.rb +21 -2
  15. data/lib/bolt/applicator.rb +19 -7
  16. data/lib/bolt/apply_inventory.rb +4 -0
  17. data/lib/bolt/apply_target.rb +4 -0
  18. data/lib/bolt/bolt_option_parser.rb +4 -3
  19. data/lib/bolt/catalog.rb +81 -68
  20. data/lib/bolt/cli.rb +16 -5
  21. data/lib/bolt/config.rb +216 -75
  22. data/lib/bolt/config/transport/ssh.rb +130 -91
  23. data/lib/bolt/executor.rb +14 -1
  24. data/lib/bolt/inventory/group.rb +1 -1
  25. data/lib/bolt/inventory/inventory.rb +4 -0
  26. data/lib/bolt/inventory/target.rb +4 -0
  27. data/lib/bolt/outputter.rb +3 -0
  28. data/lib/bolt/outputter/rainbow.rb +80 -0
  29. data/lib/bolt/pal.rb +3 -0
  30. data/lib/bolt/project.rb +48 -11
  31. data/lib/bolt/resource_instance.rb +10 -3
  32. data/lib/bolt/shell/powershell/snippets.rb +8 -0
  33. data/lib/bolt/transport/local/connection.rb +2 -1
  34. data/lib/bolt/transport/ssh/connection.rb +35 -0
  35. data/lib/bolt/version.rb +1 -1
  36. data/lib/bolt_spec/bolt_context.rb +1 -1
  37. data/lib/bolt_spec/run.rb +1 -1
  38. metadata +21 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ffe8fa93c2b9de411d9c2ef46c6c68deae3e10c7b711451dd1acf12034952b31
4
- data.tar.gz: 4892ece130a2c4c8d70aa6315df85c13be920c290c2e337dddf2b493180bea58
3
+ metadata.gz: 696f0fbeea26bd6bccad0ab2935e8174b871148bcae962438996e0822a216a7f
4
+ data.tar.gz: fc865179b5b437a6474e45d1c94139b892cec331fb85511c81ef19b41af4a806
5
5
  SHA512:
6
- metadata.gz: b95e67238b0af8b5ded1c2f14f1514fb5ab715ae43afbca3247c4f4224d7f403385e0b3f6072babbc90faeb8bcaa4d7cc0b79319258f509340fe765c3e9e13b7
7
- data.tar.gz: 9e1584102d3b103632e36d9c922df007c9b780d69a088b964ef59ff4162e94abdc12693e61fea42c5256a97ebf245e2dd4222d4019d98db2d658a10b4f15e317
6
+ metadata.gz: e0bf903cf991732261e779a665de009b9e45a08c911cab88df738201ebdc8a75b5d4e82cc7585956215b0e93f30b21142a37401f94d5ce8e0d7e41c8014bf499
7
+ data.tar.gz: ea88a182be0b972869342ef16423089338b13f9e0f540b13da4b133c231f80597646c4f920d254184e5c5b276c08ec6ec6eff0c79aeb456dfd777e68ba4433fc
data/Puppetfile CHANGED
@@ -33,7 +33,7 @@ mod 'puppetlabs-ruby_plugin_helper', '0.1.0'
33
33
  mod 'puppetlabs-aws_inventory', '0.5.0'
34
34
  mod 'puppetlabs-azure_inventory', '0.3.0'
35
35
  mod 'puppetlabs-gcloud_inventory', '0.1.1'
36
- mod 'puppetlabs-pkcs7', '0.1.0'
36
+ mod 'puppetlabs-pkcs7', '0.1.1'
37
37
  mod 'puppetlabs-terraform', '0.5.0'
38
38
  mod 'puppetlabs-vault', '0.3.0'
39
39
  mod 'puppetlabs-yaml', '0.2.0'
@@ -11,12 +11,13 @@ Puppet::DataTypes.create_type('ResourceInstance') do
11
11
  'events' => Optional[Array[Hash[String[1], Data]]]
12
12
  },
13
13
  functions => {
14
- add_event => Callable[[Hash[String[1], Data]], [Hash[String[1], Data]]],
14
+ add_event => Callable[[Hash[String[1], Data]], Array[Hash[String[1], Data]]],
15
15
  set_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
16
16
  overwrite_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
17
17
  set_desired_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
18
18
  overwrite_desired_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
19
- reference => Callable[[], String]
19
+ reference => Callable[[], String],
20
+ '[]' => Callable[[String[1]], Data]
20
21
  }
21
22
  PUPPET
22
23
 
@@ -3,7 +3,7 @@
3
3
  require 'bolt/task'
4
4
 
5
5
  # Installs the `puppet-agent` package on targets if needed, then collects facts,
6
- # including any custom facts found in Bolt's modulepath. The package is
6
+ # including any custom facts found in Bolt's module path. The package is
7
7
  # installed using either the configured plugin or the `task` plugin with the
8
8
  # `puppet_agent::install` task.
9
9
  #
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'bolt/task'
4
4
 
5
- # Query the state of resources on a list of targets using resource definitions in Bolt's modulepath.
5
+ # Query the state of resources on a list of targets using resource definitions in Bolt's module path.
6
6
  # The results are returned as a list of hashes representing each resource.
7
7
  #
8
8
  # Requires the Puppet Agent be installed on the target, which can be accomplished with apply_prep
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Lookup a resource in the target's data.
4
+ #
5
+ # For more information about resources see [the
6
+ # documentation](https://puppet.com/docs/puppet/latest/lang_resources.html).
7
+ #
8
+ # > **Note:** The `ResourceInstance` data type is under active development and is subject to
9
+ # change. You can read more about the data type in the [experimental features
10
+ # documentation](experimental_features.md#resourceinstance-data-type).
11
+ Puppet::Functions.create_function(:resource) do
12
+ # Lookup a resource in the target's data.
13
+ # @param target The Target object to add resources to. See {get_targets}.
14
+ # @param type The type of the resource
15
+ # @param title The title of the resource
16
+ # @return The ResourceInstance if found, or Undef
17
+ # @example Get the openssl package resource
18
+ # $target.apply_prep
19
+ # $resources = $target.get_resources(Package).first['resources']
20
+ # $target.set_resources($resources)
21
+ # $openssl = $target.resource('Package', 'openssl')
22
+ dispatch :resource do
23
+ param 'Target', :target
24
+ param 'Type[Resource]', :type
25
+ param 'String[1]', :title
26
+ return_type 'Optional[ResourceInstance]'
27
+ end
28
+
29
+ # Lookup a resource in the target's data, referring to resource as a string
30
+ # @param target The Target object to add resources to. See {get_targets}.
31
+ # @param type The type of the resource
32
+ # @param title The title of the resource
33
+ # @return The ResourceInstance if found, or Undef
34
+ dispatch :resource_from_string do
35
+ param 'Target', :target
36
+ param 'String[1]', :type
37
+ param 'String[1]', :title
38
+ return_type 'Optional[ResourceInstance]'
39
+ end
40
+
41
+ def resource(target, type, title)
42
+ inventory = Puppet.lookup(:bolt_inventory)
43
+ executor = Puppet.lookup(:bolt_executor) { nil }
44
+ executor&.report_function_call(self.class.name)
45
+
46
+ inventory.resource(target, type, title)
47
+ end
48
+
49
+ def resource_from_string(target, type, title)
50
+ resource(target, type, title)
51
+ end
52
+ end
@@ -109,6 +109,15 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
109
109
  end
110
110
  end
111
111
 
112
+ # Wrap Sensitive parameters for plans that are run from the CLI, as it's impossible to pass
113
+ # a Sensitive value that way. We don't do this for plans run from the run_plan function, as
114
+ # it can receive Sensitive values as arguments.
115
+ # This should only happen after expanding target params, otherwise things will blow up if
116
+ # the targets are wrapped as Sensitive. Hopefully nobody does that, though...
117
+ if options[:bolt_api_call]
118
+ params = wrap_sensitive_parameters(params, closure.parameters)
119
+ end
120
+
112
121
  # wrap plan execution in logging messages
113
122
  executor.log_plan(plan_name) do
114
123
  result = nil
@@ -169,6 +178,62 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
169
178
  end
170
179
  end
171
180
 
181
+ # Wrap any Sensitive parameters in the Sensitive wrapper type, unless they are already
182
+ # wrapped as Sensitive. This will also raise a helpful warning if the type expression
183
+ # is a complex data type using Sensitive, as we don't handle those cases.
184
+ def wrap_sensitive_parameters(params, param_models)
185
+ models = param_models.each_with_object({}) { |param, acc| acc[param.name] = param }
186
+
187
+ params.each_with_object({}) do |(name, value), acc|
188
+ model = models[name]
189
+
190
+ # Parameters passed to a plan that the plan is not expecting don't have a model,
191
+ # so keep the parameter as-is.
192
+ if model.nil?
193
+ acc[name] = value
194
+ elsif sensitive_type?(model.type_expr)
195
+ acc[name] = Puppet::Pops::Types::PSensitiveType::Sensitive.new(value)
196
+ else
197
+ if model.type_expr.to_s.include?('Sensitive')
198
+ # Include the location for regular plans. YAML plans don't have this info, so
199
+ # the location will be suppressed.
200
+ file = defined?(model.file) ? model.file : :default
201
+ line = defined?(model.line) ? model.line : :default
202
+
203
+ Puppet.warn_once(
204
+ 'unsupported_sensitive_type',
205
+ name,
206
+ "Parameter '#{name}' is a complex type using Sensitive, unable to automatically wrap as Sensitive",
207
+ file,
208
+ line
209
+ )
210
+ end
211
+
212
+ acc[name] = value
213
+ end
214
+ end
215
+ end
216
+
217
+ # Whether the type is a supported Sensitive type. We only support wrapping parameterized
218
+ # and non-parameterized Sensitive types (e.g. Sensitive, Sensitive[String])
219
+ def sensitive_type?(type_expr)
220
+ # Parameterized Sensitive type (e.g. Sensitive[String])
221
+ # left_expr is defined whenever the type is parameterized. If this is a parameterized
222
+ # Sensitive type, then we check the cased_value, which is the stringified version of
223
+ # the left expression's type.
224
+ (defined?(type_expr.left_expr) && type_expr.left_expr.cased_value == 'Sensitive') ||
225
+ # Non-parameterized Sensitive type (Sensitive)
226
+ # cased_value is defined whenever the type is non-parameterized. If the type expression
227
+ # defines cased_value, then this is a simple type and we just need to check that it's
228
+ # Sensitive.
229
+ (defined?(type_expr.cased_value) && type_expr.cased_value == 'Sensitive') ||
230
+ # Sensitive type from YAML plans
231
+ # Type expressions from YAML plans are a different class than those from regular plans.
232
+ # As long as the type expression is PSensitiveType we can be sure that the type is
233
+ # either a parameterized or non-parameterized Sensitive type.
234
+ type_expr.instance_of?(Puppet::Pops::Types::PSensitiveType)
235
+ end
236
+
172
237
  def targets_to_param(targets, params, param_types)
173
238
  nodes_param = param_types.include?('nodes')
174
239
  targets_param = param_types['targets']&.any? { |p| p.match?(/TargetSpec/) }
@@ -88,8 +88,10 @@ Puppet::Functions.create_function(:run_task) do
88
88
 
89
89
  task = Bolt::Task.from_task_signature(task_signature)
90
90
 
91
- # Set the default value for any params that have one and were not provided
92
- params = task.parameter_defaults.merge(params)
91
+ # Set the default value for any params that have one and were not provided or are undef
92
+ params = task.parameter_defaults.merge(params) do |_, default, passed|
93
+ passed.nil? ? default : passed
94
+ end
93
95
 
94
96
  task_signature.runnable_with?(params) do |mismatch_message|
95
97
  raise with_stack(:TYPE_MISMATCH, mismatch_message)
@@ -124,7 +124,10 @@ Puppet::Functions.create_function(:run_task_with) do
124
124
  # If parameters are mismatched, create a failing result for the target that will later
125
125
  # be added to the ResultSet.
126
126
  unless pcp_only
127
- params = task.parameter_defaults.merge(params)
127
+ # Set the default value for any params that have one and were not provided or are undef
128
+ params = task.parameter_defaults.merge(params) do |_, default, passed|
129
+ passed.nil? ? default : passed
130
+ end
128
131
 
129
132
  type_match = task_signature.runnable_with?(params) do |mismatch_message|
130
133
  exception = with_stack(:TYPE_MISMATCH, mismatch_message)
@@ -157,7 +160,10 @@ Puppet::Functions.create_function(:run_task_with) do
157
160
  end
158
161
  end
159
162
 
160
- mapping[target] = task.parameter_defaults.merge(params)
163
+ # Set the default value for any params that have one and were not provided or are undef
164
+ mapping[target] = task.parameter_defaults.merge(params) do |_, default, passed|
165
+ passed.nil? ? default : passed
166
+ end
161
167
  end
162
168
 
163
169
  # Add a noop parameter if the function was called with the noop metaparameter.
@@ -14,26 +14,61 @@ require 'bolt/error'
14
14
  #
15
15
  # > **Note:** Not available in apply block
16
16
  Puppet::Functions.create_function(:set_resources) do
17
- # Set multiple resources
18
- # @param target The `Target` object to add resources to. See {get_targets}.
19
- # @param resources The resources to set on the target.
20
- # @return The added `ResourceInstance` objects.
21
- # @example Add multiple resources to a target with an array of `ResourceInstance` objects.
22
- # $resource1 = ResourceInstance.new(
17
+ # Set a single resource from a data hash.
18
+ # @param target The `Target` object to add a resource to. See {get_targets}.
19
+ # @param resource The resource data hash used to set a resource on the target.
20
+ # @return An array with the added `ResourceInstance` object.
21
+ # @example Add a resource to a target from a data hash.
22
+ # $resource_hash = {
23
+ # 'type' => File,
24
+ # 'title' => '/etc/puppetlabs',
25
+ # 'state' => { 'ensure' => 'present' }
26
+ # }
27
+ #
28
+ # $target.set_resources($resource_hash)
29
+ dispatch :set_single_resource_from_hash do
30
+ param 'Target', :target
31
+ param 'Hash', :resource
32
+ return_type 'Array[ResourceInstance]'
33
+ end
34
+
35
+ # Set a single resource from a `ResourceInstance` object
36
+ # @param target The `Target` object to add a resource to. See {get_targets}.
37
+ # @param resource The `ResourceInstance` object to set on the target.
38
+ # @return An array with the added `ResourceInstance` object.
39
+ # @example Add a resource to a target from a `ResourceInstance` object.
40
+ # $resource_instance = ResourceInstance.new(
23
41
  # 'target' => $target,
24
- # 'type' => 'file',
42
+ # 'type' => File,
25
43
  # 'title' => '/etc/puppetlabs',
26
44
  # 'state' => { 'ensure' => 'present' }
27
45
  # )
28
- # $resource2 = ResourceInstance.new(
29
- # 'target' => $target,
30
- # 'type' => 'package',
31
- # 'title' => 'openssl',
32
- # 'state' => { 'ensure' => 'installed' }
33
- # )
34
- # $target.set_resources([$resource1, $resource2])
46
+ #
47
+ # $target.set_resources($resource_instance)
48
+ dispatch :set_single_resource_from_object do
49
+ param 'Target', :target
50
+ param 'ResourceInstance', :resource
51
+ return_type 'Array[ResourceInstance]'
52
+ end
53
+
54
+ # Set multiple resources from an array of data hashes and `ResourceInstance` objects.
55
+ # @param target The `Target` object to add resources to. See {get_targets}.
56
+ # @param resources The resource data hashes and `ResourceInstance` objects to set on the target.
57
+ # @return An array of the added `ResourceInstance` objects.
58
+ # @example Add resources from resource data hashes returned from an apply block.
59
+ # $apply_results = apply($targets) {
60
+ # File { '/etc/puppetlabs':
61
+ # ensure => present
62
+ # }
63
+ # Package { 'openssl':
64
+ # ensure => installed
65
+ # }
66
+ # }
67
+ #
68
+ # $apply_results.each |$result| {
69
+ # $result.target.set_resources($result.report['resource_statuses'].values)
70
+ # }
35
71
  # @example Add resources retrieved with [`get_resources`](#get_resources) to a target.
36
- # $target.apply_prep
37
72
  # $resources = $target.get_resources(Package).first['resources']
38
73
  # $target.set_resources($resources)
39
74
  dispatch :set_resources do
@@ -42,21 +77,12 @@ Puppet::Functions.create_function(:set_resources) do
42
77
  return_type 'Array[ResourceInstance]'
43
78
  end
44
79
 
45
- # Set a single resource
46
- # @param target The `Target` object to add resources to. See {get_targets}.
47
- # @param resource The resource to set on the target.
48
- # @return The added `ResourceInstance` object.
49
- # @example Add a single resource to a target with a resource data hash.
50
- # $resource = {
51
- # 'type' => 'file',
52
- # 'title' => '/etc/puppetlabs',
53
- # 'state' => { 'ensure' => 'present' }
54
- # }
55
- # $target.set_resources($resource)
56
- dispatch :set_resource do
57
- param 'Target', :target
58
- param 'Variant[Hash, ResourceInstance]', :resource
59
- return_type 'Array[ResourceInstance]'
80
+ def set_single_resource_from_hash(target, resource)
81
+ set_resources(target, [resource])
82
+ end
83
+
84
+ def set_single_resource_from_object(target, resource)
85
+ set_resources(target, [resource])
60
86
  end
61
87
 
62
88
  def set_resources(target, resources)
@@ -68,11 +94,8 @@ Puppet::Functions.create_function(:set_resources) do
68
94
  )
69
95
  end
70
96
 
97
+ Puppet.lookup(:bolt_executor).report_function_call(self.class.name)
71
98
  inventory = Puppet.lookup(:bolt_inventory)
72
- executor = Puppet.lookup(:bolt_executor)
73
- executor.report_function_call(self.class.name)
74
-
75
- inventory_target = inventory.get_target(target)
76
99
 
77
100
  resources.uniq.map do |resource|
78
101
  if resource.is_a?(Hash)
@@ -81,15 +104,18 @@ Puppet::Functions.create_function(:set_resources) do
81
104
  resource_target = if resource.key?('target')
82
105
  inventory.get_target(resource['target'])
83
106
  else
84
- inventory_target
107
+ target
85
108
  end
86
109
 
87
110
  # Observed state from get_resources() is under the 'parameters' key
88
111
  resource_state = resource['state'] || resource['parameters']
89
112
 
113
+ # Type from apply results is under the 'resource_type' key
114
+ resource_type = resource['type'] || resource['resource_type']
115
+
90
116
  init_hash = {
91
117
  'target' => resource_target,
92
- 'type' => resource['type'],
118
+ 'type' => resource_type,
93
119
  'title' => resource['title'],
94
120
  'state' => resource_state,
95
121
  'desired_state' => resource['desired_state'],
@@ -105,18 +131,14 @@ Puppet::Functions.create_function(:set_resources) do
105
131
  resource = call_function('new', type, init_hash)
106
132
  end
107
133
 
108
- unless resource.target == inventory_target
134
+ unless resource.target == target
109
135
  file, line = Puppet::Pops::PuppetStack.top_of_stack
110
136
  raise Bolt::ValidationError, "Cannot set resource #{resource.reference} for target "\
111
- "#{resource.target} on target #{inventory_target}. "\
137
+ "#{resource.target} on target #{target}. "\
112
138
  "#{Puppet::Util::Errors.error_location(file, line)}"
113
139
  end
114
140
 
115
- inventory_target.set_resource(resource)
141
+ target.set_resource(resource)
116
142
  end
117
143
  end
118
-
119
- def set_resource(target, resource)
120
- set_resources(target, [resource])
121
- end
122
144
  end
@@ -9,7 +9,7 @@ Puppet::Functions.create_function(:'file::exists', Puppet::Functions::InternalFu
9
9
  # @example Check a file on disk
10
10
  # file::exists('/tmp/i_dumped_this_here')
11
11
  # @example check a file from the modulepath
12
- # file::exists('example/files/VERSION')
12
+ # file::exists('example/VERSION')
13
13
  dispatch :exists do
14
14
  scope_param
15
15
  required_param 'String', :filename
@@ -8,7 +8,7 @@ Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunc
8
8
  # @example Read a file from disk
9
9
  # file::read('/tmp/i_dumped_this_here')
10
10
  # @example Read a file from the modulepath
11
- # file::read('example/files/VERSION')
11
+ # file::read('example/VERSION')
12
12
  dispatch :read do
13
13
  scope_param
14
14
  required_param 'String', :filename
@@ -9,7 +9,7 @@ Puppet::Functions.create_function(:'file::readable', Puppet::Functions::Internal
9
9
  # @example Check a file on disk
10
10
  # file::readable('/tmp/i_dumped_this_here')
11
11
  # @example check a file from the modulepath
12
- # file::readable('example/files/VERSION')
12
+ # file::readable('example/VERSION')
13
13
  dispatch :readable do
14
14
  scope_param
15
15
  required_param 'String', :filename
@@ -29,7 +29,7 @@ module Bolt
29
29
  def self.build_client
30
30
  logger = Logging.logger[self]
31
31
  begin
32
- config_file = File.expand_path('~/.puppetlabs/bolt/analytics.yaml')
32
+ config_file = config_path(logger)
33
33
  config = load_config(config_file, logger)
34
34
  rescue ArgumentError
35
35
  config = { 'disabled' => true }
@@ -51,6 +51,25 @@ module Bolt
51
51
  NoopClient.new
52
52
  end
53
53
 
54
+ def self.config_path(logger)
55
+ path = File.expand_path(File.join('~', '.puppetlabs', 'etc', 'bolt', 'analytics.yaml'))
56
+ old_path = File.expand_path(File.join('~', '.puppetlabs', 'bolt', 'analytics.yaml'))
57
+
58
+ if File.exist?(path)
59
+ if File.exist?(old_path)
60
+ message = "Detected analytics configuration files at '#{old_path}' and '#{path}'. Loading "\
61
+ "analytics configuration from '#{path}'."
62
+ logger.warn(message)
63
+ end
64
+
65
+ path
66
+ elsif File.exist?(old_path)
67
+ old_path
68
+ else
69
+ path
70
+ end
71
+ end
72
+
54
73
  def self.load_config(filename, logger)
55
74
  if File.exist?(filename)
56
75
  YAML.load_file(filename)
@@ -59,7 +78,7 @@ module Bolt
59
78
  logger.warn <<~ANALYTICS
60
79
  Bolt collects data about how you use it. You can opt out of providing this data.
61
80
 
62
- To disable analytics data collection, add this line to ~/.puppetlabs/bolt/analytics.yaml :
81
+ To disable analytics data collection, add this line to ~/.puppetlabs/etc/bolt/analytics.yaml :
63
82
  disabled: true
64
83
 
65
84
  Read more about what data Bolt collects and why here: