bolt 2.7.0 → 2.11.1

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +4 -3
  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_task_with.rb +192 -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/applicator.rb +3 -2
  17. data/lib/bolt/apply_inventory.rb +1 -1
  18. data/lib/bolt/apply_result.rb +1 -1
  19. data/lib/bolt/apply_target.rb +11 -2
  20. data/lib/bolt/bolt_option_parser.rb +22 -6
  21. data/lib/bolt/cli.rb +52 -22
  22. data/lib/bolt/config.rb +57 -27
  23. data/lib/bolt/config/transport/base.rb +3 -3
  24. data/lib/bolt/config/transport/docker.rb +2 -0
  25. data/lib/bolt/config/transport/local.rb +2 -0
  26. data/lib/bolt/config/transport/orch.rb +4 -2
  27. data/lib/bolt/config/transport/remote.rb +2 -0
  28. data/lib/bolt/config/transport/ssh.rb +51 -2
  29. data/lib/bolt/config/transport/winrm.rb +3 -1
  30. data/lib/bolt/executor.rb +16 -0
  31. data/lib/bolt/inventory.rb +2 -1
  32. data/lib/bolt/inventory/group.rb +1 -0
  33. data/lib/bolt/inventory/inventory.rb +5 -0
  34. data/lib/bolt/inventory/target.rb +17 -1
  35. data/lib/bolt/node/output.rb +1 -1
  36. data/lib/bolt/outputter/human.rb +5 -4
  37. data/lib/bolt/outputter/json.rb +1 -1
  38. data/lib/bolt/pal.rb +32 -14
  39. data/lib/bolt/pal/yaml_plan.rb +1 -0
  40. data/lib/bolt/plugin.rb +14 -8
  41. data/lib/bolt/plugin/module.rb +40 -7
  42. data/lib/bolt/plugin/puppetdb.rb +5 -2
  43. data/lib/bolt/project.rb +135 -0
  44. data/lib/bolt/puppetdb/config.rb +16 -28
  45. data/lib/bolt/rerun.rb +1 -1
  46. data/lib/bolt/resource_instance.rb +126 -0
  47. data/lib/bolt/result.rb +46 -23
  48. data/lib/bolt/result_set.rb +2 -5
  49. data/lib/bolt/secret.rb +20 -4
  50. data/lib/bolt/shell/bash.rb +12 -5
  51. data/lib/bolt/shell/powershell.rb +12 -4
  52. data/lib/bolt/target.rb +16 -1
  53. data/lib/bolt/transport/base.rb +24 -8
  54. data/lib/bolt/transport/orch.rb +4 -0
  55. data/lib/bolt/transport/ssh.rb +6 -2
  56. data/lib/bolt/transport/ssh/connection.rb +4 -0
  57. data/lib/bolt/transport/ssh/exec_connection.rb +110 -0
  58. data/lib/bolt/transport/winrm/connection.rb +6 -2
  59. data/lib/bolt/version.rb +1 -1
  60. data/lib/bolt_server/pe/pal.rb +1 -38
  61. data/lib/bolt_server/transport_app.rb +7 -7
  62. data/lib/bolt_spec/bolt_context.rb +3 -6
  63. data/lib/bolt_spec/plans.rb +1 -1
  64. data/lib/bolt_spec/plans/mock_executor.rb +1 -0
  65. data/lib/bolt_spec/run.rb +10 -13
  66. metadata +10 -7
  67. data/lib/bolt/boltdir.rb +0 -54
  68. data/lib/bolt/plugin/pkcs7.rb +0 -104
  69. data/lib/bolt/secret/base.rb +0 -41
@@ -9,12 +9,12 @@ module Bolt
9
9
  class Base
10
10
  attr_reader :input
11
11
 
12
- def initialize(data = {}, boltdir = nil)
12
+ def initialize(data = {}, project = nil)
13
13
  assert_hash_or_config(data)
14
14
  @input = data
15
15
  @resolved = !Bolt::Util.references?(input)
16
16
  @config = resolved? ? Bolt::Util.deep_merge(defaults, filter(input)) : defaults
17
- @boltdir = boltdir
17
+ @project = project
18
18
 
19
19
  validate if resolved?
20
20
  end
@@ -62,7 +62,7 @@ module Bolt
62
62
  Bolt::Util.deep_merge(acc, layer_data)
63
63
  end
64
64
 
65
- self.class.new(merged, @boltdir)
65
+ self.class.new(merged, @project)
66
66
  end
67
67
 
68
68
  # Resolve any references in the input data, then remerge it with the defaults
@@ -7,6 +7,8 @@ module Bolt
7
7
  class Config
8
8
  module Transport
9
9
  class Docker < Base
10
+ # NOTE: All transport configuration options should have a corresponding schema definition
11
+ # in schemas/bolt-transport-definitions.json
10
12
  OPTIONS = {
11
13
  "cleanup" => { type: TrueClass,
12
14
  desc: "Whether to clean up temporary files created on targets." },
@@ -7,6 +7,8 @@ module Bolt
7
7
  class Config
8
8
  module Transport
9
9
  class Local < Base
10
+ # NOTE: All transport configuration options should have a corresponding schema definition
11
+ # in schemas/bolt-transport-definitions.json
10
12
  OPTIONS = {
11
13
  "cleanup" => { type: TrueClass,
12
14
  desc: "Whether to clean up temporary files created on targets." },
@@ -7,6 +7,8 @@ module Bolt
7
7
  class Config
8
8
  module Transport
9
9
  class Orch < Base
10
+ # NOTE: All transport configuration options should have a corresponding schema definition
11
+ # in schemas/bolt-transport-definitions.json
10
12
  OPTIONS = {
11
13
  "cacert" => { type: String,
12
14
  desc: "The path to the CA certificate." },
@@ -32,12 +34,12 @@ module Bolt
32
34
  super
33
35
 
34
36
  if @config['cacert']
35
- @config['cacert'] = File.expand_path(@config['cacert'], @boltdir)
37
+ @config['cacert'] = File.expand_path(@config['cacert'], @project)
36
38
  Bolt::Util.validate_file('cacert', @config['cacert'])
37
39
  end
38
40
 
39
41
  if @config['token-file']
40
- @config['token-file'] = File.expand_path(@config['token-file'], @boltdir)
42
+ @config['token-file'] = File.expand_path(@config['token-file'], @project)
41
43
  Bolt::Util.validate_file('token-file', @config['token-file'])
42
44
  end
43
45
  end
@@ -7,6 +7,8 @@ module Bolt
7
7
  class Config
8
8
  module Transport
9
9
  class Remote < Base
10
+ # NOTE: All transport configuration options should have a corresponding schema definition
11
+ # in schemas/bolt-transport-definitions.json
10
12
  OPTIONS = {
11
13
  "run-on" => { type: String,
12
14
  desc: "The proxy target that the task executes on." }
@@ -8,16 +8,25 @@ module Bolt
8
8
  module Transport
9
9
  class SSH < Base
10
10
  LOGIN_SHELLS = %w[sh bash zsh dash ksh powershell].freeze
11
+
12
+ # NOTE: All transport configuration options should have a corresponding schema definition
13
+ # in schemas/bolt-transport-definitions.json
11
14
  OPTIONS = {
12
15
  "cleanup" => { type: TrueClass,
16
+ external: true,
13
17
  desc: "Whether to clean up temporary files created on targets." },
14
18
  "connect-timeout" => { type: Integer,
15
19
  desc: "How long to wait when establishing connections." },
20
+ "copy-command" => { external: true,
21
+ desc: "Command to use when copying files using ssh-command. "\
22
+ "Bolt runs `<copy-command> <src> <dest>`. **This option is experimental.**" },
16
23
  "disconnect-timeout" => { type: Integer,
17
24
  desc: "How long to wait before force-closing a connection." },
18
25
  "host" => { type: String,
26
+ external: true,
19
27
  desc: "Host name." },
20
28
  "host-key-check" => { type: TrueClass,
29
+ external: true,
21
30
  desc: "Whether to perform host key validation when connecting." },
22
31
  "extensions" => { type: Array,
23
32
  desc: "List of file extensions that are accepted for scripts or tasks on Windows. "\
@@ -26,6 +35,7 @@ module Bolt
26
35
  "a `.py` script runs with `python.exe`. The extensions `.ps1`, `.rb`, and "\
27
36
  "`.pp` are always allowed and run via hard-coded executables." },
28
37
  "interpreters" => { type: Hash,
38
+ external: true,
29
39
  desc: "A map of an extension name to the absolute path of an executable, "\
30
40
  "enabling you to override the shebang defined in a task executable. The "\
31
41
  "extension can optionally be specified with the `.` character (`.py` and "\
@@ -41,26 +51,36 @@ module Bolt
41
51
  "password" => { type: String,
42
52
  desc: "Login password." },
43
53
  "port" => { type: Integer,
54
+ external: true,
44
55
  desc: "Connection port." },
45
- "private-key" => { desc: "Either the path to the private key file to use for authentication, or a "\
56
+ "private-key" => { external: true,
57
+ desc: "Either the path to the private key file to use for authentication, or a "\
46
58
  "hash with the key `key-data` and the contents of the private key." },
47
59
  "proxyjump" => { type: String,
48
60
  desc: "A jump host to proxy connections through, and an optional user to "\
49
61
  "connect with." },
50
62
  "run-as" => { type: String,
63
+ external: true,
51
64
  desc: "A different user to run commands as after login." },
52
65
  "run-as-command" => { type: Array,
66
+ external: true,
53
67
  desc: "The command to elevate permissions. Bolt appends the user and command "\
54
68
  "strings to the configured `run-as-command` before running it on the "\
55
69
  "target. This command must not require an interactive password prompt, "\
56
70
  "and the `sudo-password` option is ignored when `run-as-command` is "\
57
71
  "specified. The `run-as-command` must be specified as an array." },
58
72
  "script-dir" => { type: String,
73
+ external: true,
59
74
  desc: "The subdirectory of the tmpdir to use in place of a randomized "\
60
75
  "subdirectory for uploading and executing temporary files on the "\
61
76
  "target. It's expected that this directory already exists as a subdir "\
62
77
  "of tmpdir, which is either configured or defaults to `/tmp`." },
78
+ "ssh-command" => { external: true,
79
+ desc: "Command and flags to use when SSHing. This enables the external "\
80
+ "SSH transport which shells out to the specified command. "\
81
+ "**This option is experimental.**" },
63
82
  "sudo-executable" => { type: String,
83
+ external: true,
64
84
  desc: "The executable to use when escalating to the configured `run-as` "\
65
85
  "user. This is useful when you want to escalate using the configured "\
66
86
  "`sudo-password`, since `run-as-command` does not use `sudo-password` "\
@@ -68,14 +88,17 @@ module Bolt
68
88
  "`<sudo-executable> -S -u <user> -p custom_bolt_prompt <command>`. "\
69
89
  "**This option is experimental.**" },
70
90
  "sudo-password" => { type: String,
91
+ external: true,
71
92
  desc: "Password to use when changing users via `run-as`." },
72
93
  "tmpdir" => { type: String,
94
+ external: true,
73
95
  desc: "The directory to upload and execute temporary files on the target." },
74
96
  "tty" => { type: TrueClass,
75
97
  desc: "Request a pseudo tty for the session. This option is generally "\
76
98
  "only used in conjunction with the `run-as` option when the sudoers "\
77
99
  "policy requires a `tty`." },
78
100
  "user" => { type: String,
101
+ external: true,
79
102
  desc: "Login user." }
80
103
  }.freeze
81
104
 
@@ -99,7 +122,14 @@ module Bolt
99
122
  end
100
123
 
101
124
  if key_opt.instance_of?(String)
102
- @config['private-key'] = File.expand_path(key_opt, @boltdir)
125
+ @config['private-key'] = File.expand_path(key_opt, @project)
126
+
127
+ # We have an explicit test for this to only warn if using net-ssh transport
128
+ Bolt::Util.validate_file('ssh key', @config['private-key']) if @config['ssh-command']
129
+ end
130
+
131
+ if key_opt.instance_of?(Hash) && @config['ssh-command']
132
+ raise Bolt::ValidationError, 'private-key must be a filepath when using ssh-command'
103
133
  end
104
134
  end
105
135
 
@@ -127,6 +157,25 @@ module Bolt
127
157
  end
128
158
  end
129
159
  end
160
+
161
+ if @config['ssh-command'] && !@config['load-config']
162
+ msg = 'Cannot use external SSH transport with load-config set to false'
163
+ raise Bolt::ValidationError, msg
164
+ end
165
+
166
+ if (ssh_cmd = @config['ssh-command'])
167
+ unless ssh_cmd.is_a?(String) || ssh_cmd.is_a?(Array)
168
+ raise Bolt::ValidationError,
169
+ "ssh-command must be a String or Array, received #{ssh_cmd.class} #{ssh_cmd.inspect}"
170
+ end
171
+ end
172
+
173
+ if (copy_cmd = @config['copy-command'])
174
+ unless copy_cmd.is_a?(String) || copy_cmd.is_a?(Array)
175
+ raise Bolt::ValidationError,
176
+ "copy-command must be a String or Array, received #{copy_cmd.class} #{copy_cmd.inspect}"
177
+ end
178
+ end
130
179
  end
131
180
  end
132
181
  end
@@ -7,6 +7,8 @@ module Bolt
7
7
  class Config
8
8
  module Transport
9
9
  class WinRM < Base
10
+ # NOTE: All transport configuration options should have a corresponding schema definition
11
+ # in schemas/bolt-transport-definitions.json
10
12
  OPTIONS = {
11
13
  "basic-auth-only" => { type: TrueClass,
12
14
  desc: "Force basic authentication. This option is only available when using SSL." },
@@ -71,7 +73,7 @@ module Bolt
71
73
  end
72
74
 
73
75
  if @config['cacert']
74
- @config['cacert'] = File.expand_path(@config['cacert'], @boltdir)
76
+ @config['cacert'] = File.expand_path(@config['cacert'], @project)
75
77
  Bolt::Util.validate_file('cacert', @config['cacert'])
76
78
  end
77
79
  end
@@ -278,6 +278,22 @@ module Bolt
278
278
  end
279
279
  end
280
280
 
281
+ def run_task_with(target_mapping, task, options = {})
282
+ targets = target_mapping.keys
283
+ description = options.fetch(:description, "task #{task.name}")
284
+
285
+ log_action(description, targets) do
286
+ options[:run_as] = run_as if run_as && !options.key?(:run_as)
287
+ target_mapping.each_value { |arguments| arguments['_task'] = task.name }
288
+
289
+ batch_execute(targets) do |transport, batch|
290
+ with_node_logging("Running task #{task.name}'", batch) do
291
+ transport.batch_task_with(batch, task, target_mapping, options, &method(:publish_event))
292
+ end
293
+ end
294
+ end
295
+ end
296
+
281
297
  def upload_file(targets, source, destination, options = {})
282
298
  description = options.fetch(:description, "file upload from #{source} to #{destination}")
283
299
  log_action(description, targets) do
@@ -15,6 +15,7 @@ module Bolt
15
15
 
16
16
  class ValidationError < Bolt::Error
17
17
  attr_accessor :path
18
+
18
19
  def initialize(message, offending_group)
19
20
  super(message, 'bolt.inventory/validation-error')
20
21
  @_message = message
@@ -83,7 +84,7 @@ module Bolt
83
84
 
84
85
  def self.empty
85
86
  config = Bolt::Config.default
86
- plugins = Bolt::Plugin.setup(config, nil, nil, Bolt::Analytics::NoopClient)
87
+ plugins = Bolt::Plugin.setup(config, nil)
87
88
 
88
89
  create_version({}, config.transport, config.transports, plugins)
89
90
  end
@@ -12,6 +12,7 @@ module Bolt
12
12
  # Regex used to validate group names and target aliases.
13
13
  NAME_REGEX = /\A[a-z0-9_][a-z0-9_-]*\Z/.freeze
14
14
 
15
+ # NOTE: All keys should have a corresponding schema property in schemas/bolt-inventory.schema.json
15
16
  DATA_KEYS = %w[config facts vars features plugin_hooks].freeze
16
17
  TARGET_KEYS = DATA_KEYS + %w[name alias uri]
17
18
  GROUP_KEYS = DATA_KEYS + %w[name groups targets]
@@ -7,6 +7,7 @@ module Bolt
7
7
  class Inventory
8
8
  class Inventory
9
9
  attr_reader :targets, :plugins, :config, :transport
10
+
10
11
  class WildcardError < Bolt::Error
11
12
  def initialize(target)
12
13
  super("Found 0 targets matching wildcard pattern #{target}", 'bolt.inventory/wildcard-error')
@@ -318,6 +319,10 @@ module Bolt
318
319
  def target_config(target)
319
320
  @targets[target.name].config
320
321
  end
322
+
323
+ def resources(target)
324
+ @targets[target.name].resources
325
+ end
321
326
  end
322
327
  end
323
328
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bolt/resource_instance'
4
+
3
5
  module Bolt
4
6
  class Inventory
5
7
  # This class represents the active state of a target within the inventory.
6
8
  class Target
7
- attr_reader :name, :uri, :safe_name, :target_alias
9
+ attr_reader :name, :uri, :safe_name, :target_alias, :resources
8
10
 
9
11
  def initialize(target_data, inventory)
10
12
  unless target_data['name'] || target_data['uri']
@@ -36,12 +38,26 @@ module Bolt
36
38
  # When alias is specified in a plan, the key will be `target_alias`, when
37
39
  # alias is specified in inventory the key will be `alias`.
38
40
  @target_alias = target_data['target_alias'] || target_data['alias'] || []
41
+ @resources = {}
39
42
 
40
43
  @inventory = inventory
41
44
 
42
45
  validate
43
46
  end
44
47
 
48
+ # rubocop:disable Naming/AccessorMethodName
49
+ def set_resource(resource)
50
+ if (existing_resource = resources[resource.reference])
51
+ existing_resource.overwrite_state(resource.state)
52
+ existing_resource.overwrite_desired_state(resource.desired_state)
53
+ existing_resource.events = existing_resource.events + resource.events
54
+ existing_resource
55
+ else
56
+ @resources[resource.reference] = resource
57
+ end
58
+ end
59
+ # rubocop:enable Naming/AccessorMethodName
60
+
45
61
  def vars
46
62
  group_cache['vars'].merge(@vars)
47
63
  end
@@ -12,7 +12,7 @@ module Bolt
12
12
  def initialize
13
13
  @stdout = StringIO.new
14
14
  @stderr = StringIO.new
15
- @exit_code = 'unkown'
15
+ @exit_code = 'unknown'
16
16
  end
17
17
  end
18
18
  end
@@ -107,13 +107,14 @@ module Bolt
107
107
 
108
108
  # Use special handling if the result looks like a command or script result
109
109
  if result.generic_value.keys == %w[stdout stderr exit_code]
110
- unless result['stdout'].strip.empty?
110
+ safe_value = result.safe_value
111
+ unless safe_value['stdout'].strip.empty?
111
112
  @stream.puts(indent(2, "STDOUT:"))
112
- @stream.puts(indent(4, result['stdout']))
113
+ @stream.puts(indent(4, safe_value['stdout']))
113
114
  end
114
- unless result['stderr'].strip.empty?
115
+ unless safe_value['stderr'].strip.empty?
115
116
  @stream.puts(indent(2, "STDERR:"))
116
- @stream.puts(indent(4, result['stderr']))
117
+ @stream.puts(indent(4, safe_value['stderr']))
117
118
  end
118
119
  elsif result.generic_value.any?
119
120
  @stream.puts(indent(2, ::JSON.pretty_generate(result.generic_value)))
@@ -28,7 +28,7 @@ module Bolt
28
28
 
29
29
  def print_result(result)
30
30
  @stream.puts ',' if @preceding_item
31
- @stream.puts result.status_hash.to_json
31
+ @stream.puts result.to_json
32
32
  @preceding_item = true
33
33
  end
34
34
 
@@ -40,7 +40,7 @@ module Bolt
40
40
  attr_reader :modulepath
41
41
 
42
42
  def initialize(modulepath, hiera_config, resource_types, max_compiles = Etc.nprocessors,
43
- trusted_external = nil, apply_settings = {})
43
+ trusted_external = nil, apply_settings = {}, project = nil)
44
44
  # Nothing works without initialized this global state. Reinitializing
45
45
  # is safe and in practice only happens in tests
46
46
  self.class.load_puppet
@@ -52,6 +52,7 @@ module Bolt
52
52
  @apply_settings = apply_settings
53
53
  @max_compiles = max_compiles
54
54
  @resource_types = resource_types
55
+ @project = project
55
56
 
56
57
  @logger = Logging.logger[self]
57
58
  if modulepath && !modulepath.empty?
@@ -114,7 +115,7 @@ module Bolt
114
115
  compiler.evaluate_string('type PlanResult = Boltlib::PlanResult')
115
116
  end
116
117
 
117
- # Register all resource types defined in $Boltdir/.resource_types as well as
118
+ # Register all resource types defined in $Project/.resource_types as well as
118
119
  # the built in types registered with the runtime_3_init method.
119
120
  def register_resource_types(loaders)
120
121
  static_loader = loaders.static_loader
@@ -136,19 +137,34 @@ module Bolt
136
137
  # TODO: If we always call this inside a bolt_executor we can remove this here
137
138
  setup
138
139
  r = Puppet::Pal.in_tmp_environment('bolt', modulepath: @modulepath, facts: {}) do |pal|
139
- pal.with_script_compiler do |compiler|
140
- alias_types(compiler)
141
- register_resource_types(Puppet.lookup(:loaders)) if @resource_types
142
- begin
143
- Puppet.override(yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
140
+ Puppet.override(bolt_project: @project,
141
+ yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
142
+ pal.with_script_compiler do |compiler|
143
+ alias_types(compiler)
144
+ register_resource_types(Puppet.lookup(:loaders)) if @resource_types
145
+ begin
144
146
  yield compiler
147
+ rescue Bolt::Error => e
148
+ e
149
+ rescue Puppet::DataBinding::LookupError => e
150
+ if e.issue_code == :HIERA_UNDEFINED_VARIABLE
151
+ message = "Interpolations are not supported in lookups outside of an apply block: #{e.message}"
152
+ PALError.new(message)
153
+ else
154
+ PALError.from_preformatted_error(e)
155
+ end
156
+ rescue Puppet::PreformattedError => e
157
+ if e.issue_code == :UNKNOWN_VARIABLE &&
158
+ %w[facts trusted server_facts settings].include?(e.arguments[:name])
159
+ message = "Evaluation Error: Variable '#{e.arguments[:name]}' is not available in the current scope "\
160
+ "unless explicitly defined. (file: #{e.file}, line: #{e.line}, column: #{e.pos})"
161
+ PALError.new(message)
162
+ else
163
+ PALError.from_preformatted_error(e)
164
+ end
165
+ rescue StandardError => e
166
+ PALError.from_preformatted_error(e)
145
167
  end
146
- rescue Bolt::Error => e
147
- e
148
- rescue Puppet::PreformattedError => e
149
- PALError.from_preformatted_error(e)
150
- rescue StandardError => e
151
- PALError.from_preformatted_error(e)
152
168
  end
153
169
  end
154
170
  end
@@ -215,6 +231,7 @@ module Bolt
215
231
  Puppet.initialize_settings(cli)
216
232
  Puppet::GettextConfig.create_default_text_domain
217
233
  Puppet[:trusted_external_command] = @trusted_external
234
+ Puppet.settings[:hiera_config] = @hiera_config
218
235
  self.class.configure_logging
219
236
  yield
220
237
  end
@@ -312,7 +329,8 @@ module Bolt
312
329
  raise Bolt::Error.unknown_plan(plan_name)
313
330
  end
314
331
 
315
- mod = plan_sig.instance_variable_get(:@plan_func).loader.parent.path
332
+ # path may be a Pathname object, so make sure to stringify it
333
+ mod = plan_sig.instance_variable_get(:@plan_func).loader.parent.path.to_s
316
334
 
317
335
  # If it's a Puppet language plan, use strings to extract data. The only
318
336
  # way to tell is to check which filename exists in the module.