bolt 2.13.0 → 2.18.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +20 -9
  6. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +1 -0
  8. data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +1 -0
  9. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +1 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +1 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +1 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +1 -0
  14. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
  15. data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +1 -0
  16. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +53 -0
  17. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +3 -0
  18. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +7 -2
  19. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +7 -4
  20. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +2 -1
  21. data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +1 -0
  22. data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +1 -0
  23. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +1 -0
  24. data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +1 -0
  25. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -0
  26. data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +1 -0
  27. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +1 -0
  28. data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +1 -0
  29. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
  30. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
  31. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +2 -0
  32. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +2 -1
  33. data/bolt-modules/file/lib/puppet/functions/file/join.rb +2 -0
  34. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -1
  35. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +3 -1
  36. data/bolt-modules/file/lib/puppet/functions/file/write.rb +2 -0
  37. data/bolt-modules/out/lib/puppet/functions/out/message.rb +2 -0
  38. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +1 -0
  39. data/bolt-modules/system/lib/puppet/functions/system/env.rb +2 -0
  40. data/lib/bolt/applicator.rb +36 -21
  41. data/lib/bolt/apply_inventory.rb +4 -0
  42. data/lib/bolt/apply_result.rb +1 -1
  43. data/lib/bolt/apply_target.rb +4 -0
  44. data/lib/bolt/bolt_option_parser.rb +27 -16
  45. data/lib/bolt/catalog.rb +79 -69
  46. data/lib/bolt/cli.rb +78 -58
  47. data/lib/bolt/config.rb +163 -136
  48. data/lib/bolt/config/options.rb +474 -0
  49. data/lib/bolt/config/transport/base.rb +16 -16
  50. data/lib/bolt/config/transport/docker.rb +9 -23
  51. data/lib/bolt/config/transport/local.rb +6 -44
  52. data/lib/bolt/config/transport/options.rb +460 -0
  53. data/lib/bolt/config/transport/orch.rb +9 -18
  54. data/lib/bolt/config/transport/remote.rb +3 -6
  55. data/lib/bolt/config/transport/ssh.rb +78 -119
  56. data/lib/bolt/config/transport/winrm.rb +18 -47
  57. data/lib/bolt/executor.rb +14 -1
  58. data/lib/bolt/inventory/group.rb +1 -1
  59. data/lib/bolt/inventory/inventory.rb +4 -14
  60. data/lib/bolt/inventory/target.rb +22 -5
  61. data/lib/bolt/logger.rb +15 -1
  62. data/lib/bolt/outputter.rb +3 -0
  63. data/lib/bolt/outputter/rainbow.rb +84 -0
  64. data/lib/bolt/pal.rb +23 -9
  65. data/lib/bolt/project.rb +75 -55
  66. data/lib/bolt/resource_instance.rb +5 -1
  67. data/lib/bolt/shell/bash.rb +30 -42
  68. data/lib/bolt/shell/powershell.rb +13 -8
  69. data/lib/bolt/shell/powershell/snippets.rb +23 -6
  70. data/lib/bolt/transport/docker.rb +9 -5
  71. data/lib/bolt/transport/local/connection.rb +2 -1
  72. data/lib/bolt/transport/orch.rb +8 -0
  73. data/lib/bolt/transport/ssh.rb +7 -1
  74. data/lib/bolt/transport/ssh/connection.rb +35 -0
  75. data/lib/bolt/transport/ssh/exec_connection.rb +1 -1
  76. data/lib/bolt/version.rb +1 -1
  77. data/lib/bolt_spec/bolt_context.rb +1 -1
  78. data/lib/bolt_spec/run.rb +1 -1
  79. metadata +22 -18
@@ -7,24 +7,15 @@ 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
12
- OPTIONS = {
13
- "cacert" => { type: String,
14
- desc: "The path to the CA certificate." },
15
- "host" => { type: String,
16
- desc: "Host name." },
17
- "job-poll-interval" => { type: Integer,
18
- desc: "Set interval to poll orchestrator for job status." },
19
- "job-poll-timeout" => { type: Integer,
20
- desc: "Set time to wait for orchestrator job status." },
21
- "service-url" => { type: String,
22
- desc: "The URL of the orchestrator API." },
23
- "task-environment" => { type: String,
24
- desc: "The environment the orchestrator loads task code from." },
25
- "token-file" => { type: String,
26
- desc: "The path to the token file." }
27
- }.freeze
10
+ OPTIONS = %w[
11
+ cacert
12
+ host
13
+ job-poll-interval
14
+ job-poll-timeout
15
+ service-url
16
+ task-environment
17
+ token-file
18
+ ].freeze
28
19
 
29
20
  DEFAULTS = {
30
21
  "task-environment" => "production"
@@ -7,12 +7,9 @@ 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
12
- OPTIONS = {
13
- "run-on" => { type: String,
14
- desc: "The proxy target that the task executes on." }
15
- }.freeze
10
+ OPTIONS = %w[
11
+ run-on
12
+ ].freeze
16
13
 
17
14
  DEFAULTS = {
18
15
  "run-on" => "localhost"
@@ -7,110 +7,82 @@ module Bolt
7
7
  class Config
8
8
  module Transport
9
9
  class SSH < Base
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
14
- OPTIONS = {
15
- "cleanup" => { type: TrueClass,
16
- external: true,
17
- desc: "Whether to clean up temporary files created on targets." },
18
- "connect-timeout" => { type: Integer,
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.**" },
23
- "disconnect-timeout" => { type: Integer,
24
- desc: "How long to wait before force-closing a connection." },
25
- "host" => { type: String,
26
- external: true,
27
- desc: "Host name." },
28
- "host-key-check" => { type: TrueClass,
29
- external: true,
30
- desc: "Whether to perform host key validation when connecting." },
31
- "extensions" => { type: Array,
32
- desc: "List of file extensions that are accepted for scripts or tasks on Windows. "\
33
- "Scripts with these file extensions rely on the target's file type "\
34
- "association to run. For example, if Python is installed on the system, "\
35
- "a `.py` script runs with `python.exe`. The extensions `.ps1`, `.rb`, and "\
36
- "`.pp` are always allowed and run via hard-coded executables." },
37
- "interpreters" => { type: Hash,
38
- external: true,
39
- desc: "A map of an extension name to the absolute path of an executable, "\
40
- "enabling you to override the shebang defined in a task executable. The "\
41
- "extension can optionally be specified with the `.` character (`.py` and "\
42
- "`py` both map to a task executable `task.py`) and the extension is case "\
43
- "sensitive. When a target's name is `localhost`, Ruby tasks run with the "\
44
- "Bolt Ruby interpreter by default." },
45
- "load-config" => { type: TrueClass,
46
- desc: "Whether to load system SSH configuration." },
47
- "login-shell" => { type: String,
48
- desc: "Which login shell Bolt should expect on the target. "\
49
- "Supported shells are #{LOGIN_SHELLS.join(', ')}. "\
50
- "**This option is experimental.**" },
51
- "password" => { type: String,
52
- desc: "Login password." },
53
- "port" => { type: Integer,
54
- external: true,
55
- desc: "Connection port." },
56
- "private-key" => { external: true,
57
- desc: "Either the path to the private key file to use for authentication, or a "\
58
- "hash with the key `key-data` and the contents of the private key." },
59
- "proxyjump" => { type: String,
60
- desc: "A jump host to proxy connections through, and an optional user to "\
61
- "connect with." },
62
- "run-as" => { type: String,
63
- external: true,
64
- desc: "A different user to run commands as after login." },
65
- "run-as-command" => { type: Array,
66
- external: true,
67
- desc: "The command to elevate permissions. Bolt appends the user and command "\
68
- "strings to the configured `run-as-command` before running it on the "\
69
- "target. This command must not require an interactive password prompt, "\
70
- "and the `sudo-password` option is ignored when `run-as-command` is "\
71
- "specified. The `run-as-command` must be specified as an array." },
72
- "script-dir" => { type: String,
73
- external: true,
74
- desc: "The subdirectory of the tmpdir to use in place of a randomized "\
75
- "subdirectory for uploading and executing temporary files on the "\
76
- "target. It's expected that this directory already exists as a subdir "\
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.**" },
82
- "sudo-executable" => { type: String,
83
- external: true,
84
- desc: "The executable to use when escalating to the configured `run-as` "\
85
- "user. This is useful when you want to escalate using the configured "\
86
- "`sudo-password`, since `run-as-command` does not use `sudo-password` "\
87
- "or support prompting. The command executed on the target is "\
88
- "`<sudo-executable> -S -u <user> -p custom_bolt_prompt <command>`. "\
89
- "**This option is experimental.**" },
90
- "sudo-password" => { type: String,
91
- external: true,
92
- desc: "Password to use when changing users via `run-as`." },
93
- "tmpdir" => { type: String,
94
- external: true,
95
- desc: "The directory to upload and execute temporary files on the target." },
96
- "tty" => { type: TrueClass,
97
- desc: "Request a pseudo tty for the session. This option is generally "\
98
- "only used in conjunction with the `run-as` option when the sudoers "\
99
- "policy requires a `tty`." },
100
- "user" => { type: String,
101
- external: true,
102
- desc: "Login user." }
103
- }.freeze
10
+ # Options available when using the net-ssh-based transport
11
+ OPTIONS = %w[
12
+ cleanup
13
+ connect-timeout
14
+ disconnect-timeout
15
+ encryption-algorithms
16
+ extensions
17
+ host
18
+ host-key-algorithms
19
+ host-key-check
20
+ interpreters
21
+ kex-algorithms
22
+ load-config
23
+ login-shell
24
+ mac-algorithms
25
+ password
26
+ port
27
+ private-key
28
+ proxyjump
29
+ script-dir
30
+ tmpdir
31
+ tty
32
+ user
33
+ ].concat(RUN_AS_OPTIONS).sort.freeze
34
+
35
+ # Options available when using the native ssh transport
36
+ NATIVE_OPTIONS = %w[
37
+ cleanup
38
+ copy-command
39
+ host
40
+ host-key-check
41
+ interpreters
42
+ native-ssh
43
+ port
44
+ private-key
45
+ script-dir
46
+ ssh-command
47
+ tmpdir
48
+ user
49
+ ].concat(RUN_AS_OPTIONS).sort.freeze
104
50
 
105
51
  DEFAULTS = {
106
52
  "cleanup" => true,
107
53
  "connect-timeout" => 10,
108
- "tty" => false,
109
- "load-config" => true,
110
54
  "disconnect-timeout" => 5,
111
- "login-shell" => 'bash'
55
+ "load-config" => true,
56
+ "login-shell" => 'bash',
57
+ "tty" => false
112
58
  }.freeze
113
59
 
60
+ # The set of options available for the ssh and native ssh transports overlap, so we
61
+ # need to check which transport is used before fully initializing, otherwise options
62
+ # may not be filtered correctly.
63
+ def initialize(data = {}, project = nil)
64
+ assert_hash_or_config(data)
65
+ @native = true if data['native-ssh']
66
+ super(data, project)
67
+ end
68
+
69
+ # This method is used to filter CLI options in the Config class. This
70
+ # should include `ssh-command` so that we can later warn if the option
71
+ # is present without `native-ssh`
72
+ def self.options
73
+ %w[ssh-command native-ssh].concat(OPTIONS)
74
+ end
75
+
76
+ private def filter(unfiltered)
77
+ # Because we filter before merging config together it's impossible to
78
+ # know whether both ssh-command *and* native-ssh will be specified
79
+ # unless they are both in the filter. However, we can't add
80
+ # ssh-command to OPTIONS since that's used for documenting available
81
+ # options. This makes it so that ssh-command is preserved so we can
82
+ # warn once all config is resolved if native-ssh isn't set.
83
+ @native ? unfiltered.slice(*NATIVE_OPTIONS) : unfiltered.slice(*self.class.options)
84
+ end
85
+
114
86
  private def validate
115
87
  super
116
88
 
@@ -125,11 +97,11 @@ module Bolt
125
97
  @config['private-key'] = File.expand_path(key_opt, @project)
126
98
 
127
99
  # 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']
100
+ Bolt::Util.validate_file('ssh key', @config['private-key']) if @config['native-ssh']
129
101
  end
130
102
 
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
+ if key_opt.instance_of?(Hash) && @config['native-ssh']
104
+ raise Bolt::ValidationError, 'private-key must be a filepath when using native-ssh'
133
105
  end
134
106
  end
135
107
 
@@ -142,10 +114,11 @@ module Bolt
142
114
  "Unsupported login-shell #{@config['login-shell']}. Supported shells are #{LOGIN_SHELLS.join(', ')}"
143
115
  end
144
116
 
145
- if (run_as_cmd = @config['run-as-command'])
146
- unless run_as_cmd.all? { |n| n.is_a?(String) }
117
+ %w[encryption-algorithms host-key-algorithms kex-algorithms mac-algorithms run-as-command].each do |opt|
118
+ next unless @config.key?(opt)
119
+ unless @config[opt].all? { |n| n.is_a?(String) }
147
120
  raise Bolt::ValidationError,
148
- "run-as-command must be an Array of Strings, received #{run_as_cmd.class} #{run_as_cmd.inspect}"
121
+ "#{opt} must be an Array of Strings, received #{@config[opt].inspect}"
149
122
  end
150
123
  end
151
124
 
@@ -158,24 +131,10 @@ module Bolt
158
131
  end
159
132
  end
160
133
 
161
- if @config['ssh-command'] && !@config['load-config']
162
- msg = 'Cannot use external SSH transport with load-config set to false'
134
+ if @config['native-ssh'] && !@config['load-config']
135
+ msg = 'Cannot use native SSH transport with load-config set to false'
163
136
  raise Bolt::ValidationError, msg
164
137
  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
179
138
  end
180
139
  end
181
140
  end
@@ -7,53 +7,24 @@ 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
12
- OPTIONS = {
13
- "basic-auth-only" => { type: TrueClass,
14
- desc: "Force basic authentication. This option is only available when using SSL." },
15
- "cacert" => { type: String,
16
- desc: "The path to the CA certificate." },
17
- "cleanup" => { type: TrueClass,
18
- desc: "Whether to clean up temporary files created on targets." },
19
- "connect-timeout" => { type: Integer,
20
- desc: "How long Bolt should wait when establishing connections." },
21
- "extensions" => { type: Array,
22
- desc: "List of file extensions that are accepted for scripts or tasks. "\
23
- "Scripts with these file extensions rely on the target's file type "\
24
- "association to run. For example, if Python is installed on the system, "\
25
- "a `.py` script runs with `python.exe`. The extensions `.ps1`, `.rb`, and "\
26
- "`.pp` are always allowed and run via hard-coded executables." },
27
- "file-protocol" => { type: String,
28
- desc: "Which file transfer protocol to use. Either `winrm` or `smb`. Using `smb` is "\
29
- "recommended for large file transfers." },
30
- "host" => { type: String,
31
- desc: "Host name." },
32
- "interpreters" => { type: Hash,
33
- desc: "A map of an extension name to the absolute path of an executable, "\
34
- "enabling you to override the shebang defined in a task executable. The "\
35
- "extension can optionally be specified with the `.` character (`.py` and "\
36
- "`py` both map to a task executable `task.py`) and the extension is case "\
37
- "sensitive. When a target's name is `localhost`, Ruby tasks run with the "\
38
- "Bolt Ruby interpreter by default." },
39
- "password" => { type: String,
40
- desc: "Login password. **Required unless using Kerberos.**" },
41
- "port" => { type: Integer,
42
- desc: "Connection port." },
43
- "realm" => { type: String,
44
- desc: "Kerberos realm (Active Directory domain) to authenticate against." },
45
- "smb-port" => { type: Integer,
46
- desc: "With file-protocol set to smb, this is the port to establish a "\
47
- "connection on." },
48
- "ssl" => { type: TrueClass,
49
- desc: "When true, Bolt uses secure https connections for WinRM." },
50
- "ssl-verify" => { type: TrueClass,
51
- desc: "When true, verifies the targets certificate matches the cacert." },
52
- "tmpdir" => { type: String,
53
- desc: "The directory to upload and execute temporary files on the target." },
54
- "user" => { type: String,
55
- desc: "Login user. **Required unless using Kerberos.**" }
56
- }.freeze
10
+ OPTIONS = %w[
11
+ basic-auth-only
12
+ cacert
13
+ cleanup
14
+ connect-timeout
15
+ extensions
16
+ file-protocol
17
+ host
18
+ interpreters
19
+ password
20
+ port
21
+ realm
22
+ smb-port
23
+ ssl
24
+ ssl-verify
25
+ tmpdir
26
+ user
27
+ ].freeze
57
28
 
58
29
  DEFAULTS = {
59
30
  "basic-auth-only" => false,
@@ -34,7 +34,8 @@ module Bolt
34
34
 
35
35
  def initialize(concurrency = 1,
36
36
  analytics = Bolt::Analytics::NoopClient.new,
37
- noop = false)
37
+ noop = false,
38
+ modified_concurrency = false)
38
39
  # lazy-load expensive gem code
39
40
  require 'concurrent'
40
41
 
@@ -64,6 +65,9 @@ module Bolt
64
65
  Concurrent.global_immediate_executor
65
66
  end
66
67
  @logger.debug { "Started with #{concurrency} max thread(s)" }
68
+
69
+ @concurrency = concurrency
70
+ @warn_concurrency = modified_concurrency
67
71
  end
68
72
 
69
73
  def transport(transport)
@@ -102,6 +106,15 @@ module Bolt
102
106
  # defined by the transport. Yields each batch, along with the corresponding
103
107
  # transport, to the block in turn and returns an array of result promises.
104
108
  def queue_execute(targets)
109
+ if @warn_concurrency && targets.length > @concurrency
110
+ @warn_concurrency = false
111
+ @logger.warn("The ulimit is low, which may cause file limit issues. Default concurrency has been set to "\
112
+ "'#{@concurrency}' to mitigate those issues, which may cause Bolt to run slow. "\
113
+ "Disable this warning by configuring ulimit using 'ulimit -n <limit>' in your shell "\
114
+ "configuration, or by configuring Bolt's concurrency. "\
115
+ "See https://puppet.com/docs/bolt/latest/bolt_known_issues.html for details.")
116
+ end
117
+
105
118
  targets.group_by(&:transport).flat_map do |protocol, protocol_targets|
106
119
  transport = transport(protocol)
107
120
  report_transport(transport, protocol_targets.count)
@@ -16,7 +16,7 @@ module Bolt
16
16
  DATA_KEYS = %w[config facts vars features plugin_hooks].freeze
17
17
  TARGET_KEYS = DATA_KEYS + %w[name alias uri]
18
18
  GROUP_KEYS = DATA_KEYS + %w[name groups targets]
19
- CONFIG_KEYS = Bolt::Config::TRANSPORT_CONFIG.keys + ['transport']
19
+ CONFIG_KEYS = Bolt::Config::INVENTORY_OPTIONS.keys
20
20
 
21
21
  def initialize(input, plugins)
22
22
  @logger = Logging.logger[self]
@@ -78,20 +78,6 @@ module Bolt
78
78
  target_array.first
79
79
  end
80
80
 
81
- def self.localhost_defaults(data)
82
- defaults = {
83
- 'config' => {
84
- 'transport' => 'local',
85
- 'local' => { 'interpreters' => { '.rb' => RbConfig.ruby } }
86
- },
87
- 'features' => ['puppet-agent']
88
- }
89
- data = Bolt::Util.deep_merge(defaults, data)
90
- # If features is an empty array deep_merge won't add the puppet-agent
91
- data['features'] += ['puppet-agent'] if data['features'].empty?
92
- data
93
- end
94
-
95
81
  #### PRIVATE ####
96
82
  def group_data_for(target_name)
97
83
  @groups.group_collect(target_name)
@@ -323,6 +309,10 @@ module Bolt
323
309
  def resources(target)
324
310
  @targets[target.name].resources
325
311
  end
312
+
313
+ def resource(target, type, title)
314
+ @targets[target.name].resource(type, title)
315
+ end
326
316
  end
327
317
  end
328
318
  end
@@ -30,6 +30,10 @@ module Bolt
30
30
  @safe_name = @uri_obj.omit(:password).to_str.sub(%r{^//}, '')
31
31
  end
32
32
 
33
+ if @name == 'localhost'
34
+ target_data = localhost_defaults(target_data)
35
+ end
36
+
33
37
  @config = target_data['config'] || {}
34
38
  @vars = target_data['vars'] || {}
35
39
  @facts = target_data['facts'] || {}
@@ -45,6 +49,20 @@ module Bolt
45
49
  validate
46
50
  end
47
51
 
52
+ def localhost_defaults(data)
53
+ defaults = {
54
+ 'config' => {
55
+ 'transport' => 'local',
56
+ 'local' => { 'interpreters' => { '.rb' => RbConfig.ruby } }
57
+ },
58
+ 'features' => ['puppet-agent']
59
+ }
60
+ data = Bolt::Util.deep_merge(defaults, data)
61
+ # If features is an empty array deep_merge won't add the puppet-agent
62
+ data['features'] += ['puppet-agent'] if data['features'].empty?
63
+ data
64
+ end
65
+
48
66
  # rubocop:disable Naming/AccessorMethodName
49
67
  def set_resource(resource)
50
68
  if (existing_resource = resources[resource.reference])
@@ -90,6 +108,10 @@ module Bolt
90
108
  end
91
109
  end
92
110
 
111
+ def resource(type, title)
112
+ resources[Bolt::ResourceInstance.format_reference(type, title)]
113
+ end
114
+
93
115
  def plugin_hooks
94
116
  # Merge plugin_hooks from the config file with any defined by the group
95
117
  # or assigned dynamically to the target
@@ -214,11 +236,6 @@ module Bolt
214
236
  'target_alias' => []
215
237
  }
216
238
 
217
- # This should be handled by `get_targets`
218
- if @name == 'localhost'
219
- group_data = Bolt::Inventory::Inventory.localhost_defaults(group_data)
220
- end
221
-
222
239
  @group_cache = group_data
223
240
  end
224
241