bolt 2.8.0 → 2.12.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +2 -2
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +2 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +27 -0
  5. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -0
  6. data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +2 -0
  7. data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +4 -3
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +61 -0
  9. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +122 -0
  10. data/bolt-modules/boltlib/types/planresult.pp +12 -1
  11. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +3 -1
  12. data/bolt-modules/file/lib/puppet/functions/file/join.rb +1 -1
  13. data/bolt-modules/file/lib/puppet/functions/file/read.rb +2 -1
  14. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +3 -1
  15. data/bolt-modules/file/lib/puppet/functions/file/write.rb +3 -1
  16. data/lib/bolt/analytics.rb +21 -2
  17. data/lib/bolt/applicator.rb +3 -1
  18. data/lib/bolt/apply_result.rb +1 -1
  19. data/lib/bolt/apply_target.rb +3 -2
  20. data/lib/bolt/bolt_option_parser.rb +18 -8
  21. data/lib/bolt/cli.rb +35 -5
  22. data/lib/bolt/config.rb +45 -13
  23. data/lib/bolt/config/transport/docker.rb +2 -0
  24. data/lib/bolt/config/transport/local.rb +2 -0
  25. data/lib/bolt/config/transport/orch.rb +2 -0
  26. data/lib/bolt/config/transport/remote.rb +2 -0
  27. data/lib/bolt/config/transport/ssh.rb +50 -1
  28. data/lib/bolt/config/transport/winrm.rb +2 -0
  29. data/lib/bolt/inventory.rb +2 -1
  30. data/lib/bolt/inventory/group.rb +1 -0
  31. data/lib/bolt/inventory/inventory.rb +5 -0
  32. data/lib/bolt/inventory/target.rb +17 -1
  33. data/lib/bolt/node/output.rb +1 -1
  34. data/lib/bolt/outputter/human.rb +5 -4
  35. data/lib/bolt/outputter/json.rb +1 -1
  36. data/lib/bolt/pal.rb +4 -1
  37. data/lib/bolt/pal/yaml_plan.rb +1 -0
  38. data/lib/bolt/plugin.rb +13 -7
  39. data/lib/bolt/plugin/puppetdb.rb +5 -2
  40. data/lib/bolt/project.rb +25 -7
  41. data/lib/bolt/puppetdb/config.rb +14 -26
  42. data/lib/bolt/rerun.rb +1 -1
  43. data/lib/bolt/resource_instance.rb +126 -0
  44. data/lib/bolt/result.rb +46 -23
  45. data/lib/bolt/result_set.rb +2 -5
  46. data/lib/bolt/shell/bash.rb +1 -1
  47. data/lib/bolt/shell/powershell.rb +12 -4
  48. data/lib/bolt/target.rb +12 -1
  49. data/lib/bolt/transport/ssh.rb +6 -2
  50. data/lib/bolt/transport/ssh/connection.rb +4 -0
  51. data/lib/bolt/transport/ssh/exec_connection.rb +110 -0
  52. data/lib/bolt/transport/winrm/connection.rb +6 -2
  53. data/lib/bolt/version.rb +1 -1
  54. data/lib/bolt_server/pe/pal.rb +1 -38
  55. data/lib/bolt_server/transport_app.rb +7 -7
  56. data/lib/bolt_spec/bolt_context.rb +1 -4
  57. data/lib/bolt_spec/plans/mock_executor.rb +1 -0
  58. data/lib/bolt_spec/run.rb +2 -5
  59. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bdb245ba4347b6309414b645bea7e8829651886d3f85152217801ce7463fdfb6
4
- data.tar.gz: 4e93d16cbb9d5cd0314f1c12031cd1e7d1ca9eb0c40186119f15a902aa0732d9
3
+ metadata.gz: c36dc3edc9637ac83eab0734e6143961ff7c97f4cbc0866ce8e1b5a0b5d318c2
4
+ data.tar.gz: c52ea03e1354361836c6d76c108735e2f8525aa5e8132f87cf895b5a0f2d802d
5
5
  SHA512:
6
- metadata.gz: 5264e15f6e29f8d2e8cd9b02f67161e110215c20161c9e5ef22cf85294597eb262c40f0e13491dcb83c683b5c11930bc26fa1f8e5b80bee5ce485c60567fa8f5
7
- data.tar.gz: 3b6053135ba279dc70b41cfa13dcd7c5b00a70fa8dd2ed3370bcdd8b1e9c33b33ec2ea817bc6fc5c7afb6114a5abe1c93ea328e432c5b1748c22af04daad5a6f
6
+ metadata.gz: ded5edab2a28a3a50d2eb9c9d2a1ccad43eae0272b09a609b219a78516bd32f8bff0e35751a95629d4d918cf3a22883d47a90c5c0f402c5aeedaffaab5dfc546
7
+ data.tar.gz: acde1204c706c49926a33b074da9b5262d1e6d11d27137bec55d7ea80a3ec7e009a1defc309e1f9a7fe824db9c0d4ce92963ddc6d3ae3d2619550fc5c4164ed3
data/Puppetfile CHANGED
@@ -6,7 +6,7 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
8
  mod 'puppetlabs-service', '1.2.0'
9
- mod 'puppetlabs-puppet_agent', '3.0.2'
9
+ mod 'puppetlabs-puppet_agent', '3.2.0'
10
10
  mod 'puppetlabs-facts', '1.0.0'
11
11
 
12
12
  # Core types and providers for Puppet 6
@@ -24,7 +24,7 @@ mod 'puppetlabs-zone_core', '1.0.3'
24
24
  # Useful additional modules
25
25
  mod 'puppetlabs-package', '1.1.0'
26
26
  mod 'puppetlabs-puppet_conf', '0.6.0'
27
- mod 'puppetlabs-python_task_helper', '0.4.0'
27
+ mod 'puppetlabs-python_task_helper', '0.4.3'
28
28
  mod 'puppetlabs-reboot', '3.0.0'
29
29
  mod 'puppetlabs-ruby_task_helper', '0.5.1'
30
30
  mod 'puppetlabs-ruby_plugin_helper', '0.1.0'
@@ -17,5 +17,7 @@ Puppet::DataTypes.create_type('ApplyResult') do
17
17
 
18
18
  load_file('bolt/apply_result')
19
19
 
20
+ # Needed for Puppet to recognize Bolt::ApplyResult as a Puppet object when deserializing
21
+ Bolt::ApplyResult.include(Puppet::Pops::Types::PuppetObject)
20
22
  implementation_class Bolt::ApplyResult
21
23
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ Puppet::DataTypes.create_type('ResourceInstance') do
4
+ interface <<-PUPPET
5
+ attributes => {
6
+ 'target' => Target,
7
+ 'type' => Variant[String[1], Type[Resource]],
8
+ 'title' => String[1],
9
+ 'state' => Optional[Hash[String[1], Data]],
10
+ 'desired_state' => Optional[Hash[String[1], Data]],
11
+ 'events' => Optional[Array[Hash[String[1], Data]]]
12
+ },
13
+ functions => {
14
+ add_event => Callable[[Hash[String[1], Data]], [Hash[String[1], Data]]],
15
+ set_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
16
+ overwrite_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
17
+ set_desired_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
18
+ overwrite_desired_state => Callable[[Hash[String[1], Data]], Hash[String[1], Data]],
19
+ reference => Callable[[], String]
20
+ }
21
+ PUPPET
22
+
23
+ load_file('bolt/resource_instance')
24
+ # Needed for Puppet to recognize Bolt::ResourceInstance as a Puppet object when deserializing
25
+ Bolt::ResourceInstance.include(Puppet::Pops::Types::PuppetObject)
26
+ implementation_class Bolt::ResourceInstance
27
+ end
@@ -19,5 +19,7 @@ Puppet::DataTypes.create_type('Result') do
19
19
 
20
20
  load_file('bolt/result')
21
21
 
22
+ # Needed for Puppet to recognize Bolt::Result as a Puppet object when deserializing
23
+ Bolt::Result.include(Puppet::Pops::Types::PuppetObject)
22
24
  implementation_class Bolt::Result
23
25
  end
@@ -25,5 +25,7 @@ Puppet::DataTypes.create_type('ResultSet') do
25
25
 
26
26
  load_file('bolt/result_set')
27
27
 
28
+ # Needed for Puppet to recognize Bolt::ResultSet as a Puppet object when deserializing
29
+ Bolt::ResultSet.include(Puppet::Pops::Types::PuppetObject)
28
30
  implementation_class Bolt::ResultSet
29
31
  end
@@ -8,8 +8,6 @@ Puppet::DataTypes.create_type('Target') do
8
8
  target_implementation_class = Bolt::Target
9
9
  end
10
10
 
11
- require 'bolt/target'
12
-
13
11
  interface <<-PUPPET
14
12
  attributes => {
15
13
  uri => { type => Optional[String[1]], kind => given_or_derived },
@@ -20,7 +18,8 @@ Puppet::DataTypes.create_type('Target') do
20
18
  vars => { type => Optional[Hash[String[1], Data]], kind => given_or_derived },
21
19
  facts => { type => Optional[Hash[String[1], Data]], kind => given_or_derived },
22
20
  features => { type => Optional[Array[String[1]]], kind => given_or_derived },
23
- plugin_hooks => { type => Optional[Hash[String[1], Data]], kind => given_or_derived }
21
+ plugin_hooks => { type => Optional[Hash[String[1], Data]], kind => given_or_derived },
22
+ resources => { type => Optional[Hash[String[1], ResourceInstance]], kind => given_or_derived }
24
23
  },
25
24
  functions => {
26
25
  host => Callable[[], Optional[String]],
@@ -33,5 +32,7 @@ Puppet::DataTypes.create_type('Target') do
33
32
  }
34
33
  PUPPET
35
34
 
35
+ # Needed for Puppet to recognize targets as Puppet objects when deserializing
36
+ target_implementation_class.include(Puppet::Pops::Types::PuppetObject)
36
37
  implementation_class target_implementation_class
37
38
  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,58 @@ 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
+ if sensitive_type?(model.type_expr)
191
+ acc[name] = Puppet::Pops::Types::PSensitiveType::Sensitive.new(value)
192
+ else
193
+ if model.type_expr.to_s.include?('Sensitive')
194
+ # Include the location for regular plans. YAML plans don't have this info, so
195
+ # the location will be suppressed.
196
+ file = defined?(model.file) ? model.file : :default
197
+ line = defined?(model.line) ? model.line : :default
198
+
199
+ Puppet.warn_once(
200
+ 'unsupported_sensitive_type',
201
+ name,
202
+ "Parameter '#{name}' is a complex type using Sensitive, unable to automatically wrap as Sensitive",
203
+ file,
204
+ line
205
+ )
206
+ end
207
+
208
+ acc[name] = value
209
+ end
210
+ end
211
+ end
212
+
213
+ # Whether the type is a supported Sensitive type. We only support wrapping parameterized
214
+ # and non-parameterized Sensitive types (e.g. Sensitive, Sensitive[String])
215
+ def sensitive_type?(type_expr)
216
+ # Parameterized Sensitive type (e.g. Sensitive[String])
217
+ # left_expr is defined whenever the type is parameterized. If this is a parameterized
218
+ # Sensitive type, then we check the cased_value, which is the stringified version of
219
+ # the left expression's type.
220
+ (defined?(type_expr.left_expr) && type_expr.left_expr.cased_value == 'Sensitive') ||
221
+ # Non-parameterized Sensitive type (Sensitive)
222
+ # cased_value is defined whenever the type is non-parameterized. If the type expression
223
+ # defines cased_value, then this is a simple type and we just need to check that it's
224
+ # Sensitive.
225
+ (defined?(type_expr.cased_value) && type_expr.cased_value == 'Sensitive') ||
226
+ # Sensitive type from YAML plans
227
+ # Type expressions from YAML plans are a different class than those from regular plans.
228
+ # As long as the type expression is PSensitiveType we can be sure that the type is
229
+ # either a parameterized or non-parameterized Sensitive type.
230
+ type_expr.instance_of?(Puppet::Pops::Types::PSensitiveType)
231
+ end
232
+
172
233
  def targets_to_param(targets, params, param_types)
173
234
  nodes_param = param_types.include?('nodes')
174
235
  targets_param = param_types['targets']&.any? { |p| p.match?(/TargetSpec/) }
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ # Sets one or more ResourceInstances on a Target. This function does not apply or modify
6
+ # resources on a target.
7
+ #
8
+ # For more information about resources see [the
9
+ # documentation](https://puppet.com/docs/puppet/latest/lang_resources.html).
10
+ #
11
+ # > **Note:** The `ResourceInstance` data type is under active development and is subject to
12
+ # change. You can read more about the data type in the [experimental features
13
+ # documentation](experimental_features.md#resourceinstance-data-type).
14
+ #
15
+ # > **Note:** Not available in apply block
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(
23
+ # 'target' => $target,
24
+ # 'type' => 'file',
25
+ # 'title' => '/etc/puppetlabs',
26
+ # 'state' => { 'ensure' => 'present' }
27
+ # )
28
+ # $resource2 = ResourceInstance.new(
29
+ # 'target' => $target,
30
+ # 'type' => 'package',
31
+ # 'title' => 'openssl',
32
+ # 'state' => { 'ensure' => 'installed' }
33
+ # )
34
+ # $target.set_resources([$resource1, $resource2])
35
+ # @example Add resources retrieved with [`get_resources`](#get_resources) to a target.
36
+ # $target.apply_prep
37
+ # $resources = $target.get_resources(Package).first['resources']
38
+ # $target.set_resources($resources)
39
+ dispatch :set_resources do
40
+ param 'Target', :target
41
+ param 'Array[Variant[Hash, ResourceInstance]]', :resources
42
+ return_type 'Array[ResourceInstance]'
43
+ end
44
+
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]'
60
+ end
61
+
62
+ def set_resources(target, resources)
63
+ unless Puppet[:tasks]
64
+ raise Puppet::ParseErrorWithIssue
65
+ .from_issue_and_stack(
66
+ Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING,
67
+ action: 'set_resources'
68
+ )
69
+ end
70
+
71
+ 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
+
77
+ resources.uniq.map do |resource|
78
+ if resource.is_a?(Hash)
79
+ # ResourceInstance expects a Target object, so either get a specified target from
80
+ # the inventory or use the target this function was called on.
81
+ resource_target = if resource.key?('target')
82
+ inventory.get_target(resource['target'])
83
+ else
84
+ inventory_target
85
+ end
86
+
87
+ # Observed state from get_resources() is under the 'parameters' key
88
+ resource_state = resource['state'] || resource['parameters']
89
+
90
+ init_hash = {
91
+ 'target' => resource_target,
92
+ 'type' => resource['type'],
93
+ 'title' => resource['title'],
94
+ 'state' => resource_state,
95
+ 'desired_state' => resource['desired_state'],
96
+ 'events' => resource['events']
97
+ }
98
+
99
+ # Calling Bolt::ResourceInstance.new or Bolt::ResourceInstance.from_asserted_hash
100
+ # will not perform any validation on the parameters. Instead, we need to use the
101
+ # Puppet constructor to initialize the object, which will first validate the parameters
102
+ # and then call Bolt::ResourceInstance.from_asserted_hash internally. To do this we
103
+ # first need to get the Puppet datatype and then call the new function on that type.
104
+ type = Puppet::Pops::Types::TypeParser.singleton.parse('ResourceInstance')
105
+ resource = call_function('new', type, init_hash)
106
+ end
107
+
108
+ unless resource.target == inventory_target
109
+ file, line = Puppet::Pops::PuppetStack.top_of_stack
110
+ raise Bolt::ValidationError, "Cannot set resource #{resource.reference} for target "\
111
+ "#{resource.target} on target #{inventory_target}. "\
112
+ "#{Puppet::Util::Errors.error_location(file, line)}"
113
+ end
114
+
115
+ inventory_target.set_resource(resource)
116
+ end
117
+ end
118
+
119
+ def set_resource(target, resource)
120
+ set_resources(target, [resource])
121
+ end
122
+ end
@@ -2,4 +2,15 @@
2
2
  # should be used as the return type of functions that run plans and return the
3
3
  # results.
4
4
 
5
- type Boltlib::PlanResult = Variant[Boolean, Numeric, String, Undef, Error, Result, ResultSet, Target, Array[Boltlib::PlanResult], Hash[String, Boltlib::PlanResult]]
5
+ type Boltlib::PlanResult = Variant[Boolean,
6
+ Numeric,
7
+ String,
8
+ Undef,
9
+ Error,
10
+ Result,
11
+ ApplyResult,
12
+ ResultSet,
13
+ Target,
14
+ ResourceInstance,
15
+ Array[Boltlib::PlanResult],
16
+ Hash[String, Boltlib::PlanResult]]
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Check if a file exists.
3
+ # Check if a local file exists using Puppet's
4
+ # `Puppet::Parser::Files.find_file()` function. This will only check files that
5
+ # are on the machine Bolt is run on.
4
6
  Puppet::Functions.create_function(:'file::exists', Puppet::Functions::InternalFunction) do
5
7
  # @param filename Absolute path or Puppet file path.
6
8
  # @return Whether the file exists.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Join file paths.
3
+ # Join file paths using ruby's `File.join()` function.
4
4
  Puppet::Functions.create_function(:'file::join') do
5
5
  # @param paths The paths to join.
6
6
  # @return The joined file path.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Read a file and return its contents.
3
+ # Read a file on localhost and return its contents using ruby's `File.read`. This will
4
+ # only read files on the machine you run Bolt on.
4
5
  Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunction) do
5
6
  # @param filename Absolute path or Puppet file path.
6
7
  # @return The file's contents.
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Check if a file is readable.
3
+ # Check if a local file is readable using Puppet's
4
+ # `Puppet::Parser::Files.find_file()` function. This will only check files on the
5
+ # machine you run Bolt on.
4
6
  Puppet::Functions.create_function(:'file::readable', Puppet::Functions::InternalFunction) do
5
7
  # @param filename Absolute path or Puppet file path.
6
8
  # @return Whether the file is readable.
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Write a string to a file.
3
+ # Write a string to a file on localhost using ruby's `File.write`. This will
4
+ # only write files to the machine you run Bolt on. Use `write_file()` to write
5
+ # to remote targets.
4
6
  Puppet::Functions.create_function(:'file::write') do
5
7
  # @param filename Absolute path.
6
8
  # @param content File content to write.
@@ -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:
@@ -34,6 +34,8 @@ module Bolt
34
34
  search_dirs << mod.plugins if mod.plugins?
35
35
  search_dirs << mod.pluginfacts if mod.pluginfacts?
36
36
  search_dirs << mod.files if mod.files?
37
+ type_files = "#{mod.path}/types"
38
+ search_dirs << type_files if File.exist?(type_files)
37
39
  search_dirs
38
40
  end
39
41
  end
@@ -181,7 +183,7 @@ module Bolt
181
183
  rich_data: true,
182
184
  symbol_as_string: true,
183
185
  type_by_reference: true,
184
- local_reference: false)
186
+ local_reference: true)
185
187
 
186
188
  scope = {
187
189
  code_ast: ast,
@@ -57,7 +57,7 @@ module Bolt
57
57
  msg = "Report result contains an '_output' key. Catalog application may have printed extraneous output to stdout: #{result['_output']}"
58
58
  # rubocop:enable Layout/LineLength
59
59
  else
60
- msg = "Report did not contain all expected keys missing: #{missing_keys.join(' ,')}"
60
+ msg = "Report did not contain all expected keys missing: #{missing_keys.join(', ')}"
61
61
  end
62
62
 
63
63
  { 'msg' => msg,