bolt 0.20.7 → 0.21.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d1a5c8ec4cf105ece549f43ed95bcaed30df97d8
4
- data.tar.gz: 0b28cbab7855f68ef4ab9c797bf596e3313becf7
3
+ metadata.gz: 563c0acfed40d50d62d19a1c3d8acc4ea7ff04ed
4
+ data.tar.gz: 2233c30b95d4cf03e68a131cfe4e83ea527f720f
5
5
  SHA512:
6
- metadata.gz: 467977c61d4c83051978526cba46de367c0626dd4b3a11cb7030faeebd914b8094a66552537a4637c4fdbce2346c571f319a38be69fa29b8d3dadaed14fe14bb
7
- data.tar.gz: 00834ae333d3d0cb4cd3e5b67c22f53811fe6c4c2d89de1a20e72bd749db697140511045a405f3f0e2320aa8338c238dff7c4bf3fb3adf9c3401048a8ae7f539
6
+ metadata.gz: 94354f851af225ecd5ecc5123fee0aca0844d947685596d605e33f81543a8c5b071e30b2bcf4e872ab188e194dbec13b69dc8850de3761b02604ef2c1f49b997
7
+ data.tar.gz: 3d9e1463227a2c4ac4d0e80d6c1475e65b299f16d6371bef210f2dcbd090054a78d1590f54358fea416fdf812a7889890fb37b02ad835da81cc6d67b76046de4
@@ -18,7 +18,8 @@ module Bolt
18
18
  operating_system: :cd1,
19
19
  inventory_nodes: :cd2,
20
20
  inventory_groups: :cd3,
21
- target_nodes: :cd4
21
+ target_nodes: :cd4,
22
+ output_format: :cd5
22
23
  }.freeze
23
24
 
24
25
  def self.build_client
@@ -0,0 +1,292 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module Bolt
6
+ class BoltOptionParser < OptionParser
7
+ def self.examples(cmd, desc)
8
+ <<-EXAMP
9
+ #{desc} a Windows host via WinRM, providing for the password
10
+ bolt #{cmd} -n winrm://winhost -u Administrator -p
11
+ #{desc} the local machine, a Linux host via SSH, and hosts from a group specified in an inventory file
12
+ bolt #{cmd} -n localhost,nixhost,node_group
13
+ #{desc} Windows hosts queried from PuppetDB via WinRM as a domain user, prompting for the password
14
+ bolt #{cmd} -q 'inventory[certname] { facts.os.family = "windows" }' --transport winrm -u 'domain\\Administrator' -p
15
+ EXAMP
16
+ end
17
+
18
+ BANNER = <<-HELP
19
+ Usage: bolt <subcommand> <action> [options]
20
+
21
+ Available subcommands:
22
+ bolt command run <command> Run a command remotely
23
+ bolt file upload <src> <dest> Upload a local file
24
+ bolt script run <script> Upload a local script and run it remotely
25
+ bolt task show Show list of available tasks
26
+ bolt task show <task> Show documentation for task
27
+ bolt task run <task> [params] Run a Puppet task
28
+ bolt plan show Show list of available plans
29
+ bolt plan show <plan> Show details for plan
30
+ bolt plan run <plan> [params] Run a Puppet task plan
31
+
32
+ Run `bolt <subcommand> --help` to view specific examples.
33
+
34
+ where [options] are:
35
+ HELP
36
+
37
+ TASK_HELP = <<-HELP
38
+ Usage: bolt task <action> <task> [options] [parameters]
39
+
40
+ Available actions are:
41
+ show Show list of available tasks
42
+ show <task> Show documentation for task
43
+ run Run a Puppet task
44
+
45
+ Parameters are of the form <parameter>=<value>.
46
+
47
+ #{examples('task run facts', 'run facter on')}
48
+ Available options are:
49
+ HELP
50
+
51
+ COMMAND_HELP = <<-HELP
52
+ Usage: bolt command <action> <command> [options]
53
+
54
+ Available actions are:
55
+ run Run a command remotely
56
+
57
+ #{examples('command run hostname', 'run hostname on')}
58
+ Available options are:
59
+ HELP
60
+
61
+ SCRIPT_HELP = <<-HELP
62
+ Usage: bolt script <action> <script> [[arg1] ... [argN]] [options]
63
+
64
+ Available actions are:
65
+ run Upload a local script and run it remotely
66
+
67
+ #{examples('script run my_script.ps1 some args', 'run a script on')}
68
+ Available options are:
69
+ HELP
70
+
71
+ PLAN_HELP = <<-HELP
72
+ Usage: bolt plan <action> <plan> [options] [parameters]
73
+
74
+ Available actions are:
75
+ show Show list of available plans
76
+ show <plan> Show details for plan
77
+ run Run a Puppet task plan
78
+
79
+ Parameters are of the form <parameter>=<value>.
80
+
81
+ #{examples('plan run canary command=hostname', 'run the canary plan on')}
82
+ Available options are:
83
+ HELP
84
+
85
+ FILE_HELP = <<-HELP
86
+ Usage: bolt file <action> [options]
87
+
88
+ Available actions are:
89
+ upload <src> <dest> Upload local file <src> to <dest> on each node
90
+
91
+ #{examples('file upload /tmp/source /etc/profile.d/login.sh', 'upload a file to')}
92
+ Available options are:
93
+ HELP
94
+
95
+ # A helper mixin for OptionParser::Switch instances which will allow
96
+ # us to show/hide particular switch in the help message produced by
97
+ # the OptionParser#help method on demand.
98
+ module SwitchHider
99
+ attr_accessor :hide
100
+
101
+ def summarize(*args)
102
+ return self if hide
103
+ super
104
+ end
105
+ end
106
+
107
+ def initialize(options)
108
+ super()
109
+
110
+ @options = options
111
+
112
+ @nodes = define('-n', '--nodes NODES',
113
+ 'Identifies the nodes to target.',
114
+ 'Enter a comma-separated list of node URIs or group names.',
115
+ "Or read a node list from an input file '@<file>' or stdin '-'.",
116
+ 'Example: --nodes localhost,node_group,ssh://nix.com:23,winrm://windows.puppet.com',
117
+ 'URI format is [protocol://]host[:port]',
118
+ "SSH is the default protocol; may be #{TRANSPORTS.keys.join(', ')}",
119
+ 'For Windows nodes, specify the winrm:// protocol if it has not be configured',
120
+ 'For SSH, port defaults to `22`',
121
+ 'For WinRM, port defaults to `5985` or `5986` based on the --[no-]ssl setting') do |nodes|
122
+ @options[:nodes] << get_arg_input(nodes)
123
+ end.extend(SwitchHider)
124
+ @query = define('-q', '--query QUERY', 'Query PuppetDB to determine the targets') do |query|
125
+ @options[:query] = query
126
+ end.extend(SwitchHider)
127
+ define('--noop', 'Execute a task that supports it in noop mode') do |_|
128
+ @options[:noop] = true
129
+ end
130
+ define('--description DESCRIPTION',
131
+ 'Description to use for the job') do |description|
132
+ @options[:description] = description
133
+ end
134
+ define('--params PARAMETERS',
135
+ "Parameters to a task or plan as json, a json file '@<file>', or on stdin '-'") do |params|
136
+ @options[:task_options] = parse_params(params)
137
+ end
138
+
139
+ separator 'Authentication:'
140
+ define('-u', '--user USER', 'User to authenticate as') do |user|
141
+ @options[:user] = user
142
+ end
143
+ define('-p', '--password [PASSWORD]',
144
+ 'Password to authenticate with. Omit the value to prompt for the password.') do |password|
145
+ if password.nil?
146
+ STDOUT.print "Please enter your password: "
147
+ @options[:password] = STDIN.noecho(&:gets).chomp
148
+ STDOUT.puts
149
+ else
150
+ @options[:password] = password
151
+ end
152
+ end
153
+ define('--private-key KEY', 'Private ssh key to authenticate with') do |key|
154
+ @options[:'private-key'] = key
155
+ end
156
+ define('--[no-]host-key-check', 'Check host keys with SSH') do |host_key_check|
157
+ @options[:'host-key-check'] = host_key_check
158
+ end
159
+ define('--[no-]ssl', 'Use SSL with WinRM') do |ssl|
160
+ @options[:ssl] = ssl
161
+ end
162
+ define('--[no-]ssl-verify', 'Verify remote host SSL certificate with WinRM') do |ssl_verify|
163
+ @options[:'ssl-verify'] = ssl_verify
164
+ end
165
+
166
+ separator 'Escalation:'
167
+ define('--run-as USER', 'User to run as using privilege escalation') do |user|
168
+ @options[:'run-as'] = user
169
+ end
170
+ define('--sudo-password [PASSWORD]',
171
+ 'Password for privilege escalation. Omit the value to prompt for the password.') do |password|
172
+ if password.nil?
173
+ STDOUT.print "Please enter your privilege escalation password: "
174
+ @options[:'sudo-password'] = STDIN.noecho(&:gets).chomp
175
+ STDOUT.puts
176
+ else
177
+ @options[:'sudo-password'] = password
178
+ end
179
+ end
180
+
181
+ separator 'Run context:'
182
+ define('-c', '--concurrency CONCURRENCY', Integer,
183
+ 'Maximum number of simultaneous connections (default: 100)') do |concurrency|
184
+ @options[:concurrency] = concurrency
185
+ end
186
+ define('--modulepath MODULES',
187
+ "List of directories containing modules, separated by '#{File::PATH_SEPARATOR}'") do |modulepath|
188
+ @options[:modulepath] = modulepath.split(File::PATH_SEPARATOR)
189
+ end
190
+ define('--boltdir FILEPATH',
191
+ 'Specify what Boltdir to load config from (default: autodiscovered from current working dir)') do |path|
192
+ @options[:configfile] = path
193
+ end
194
+ define('--configfile FILEPATH',
195
+ 'Specify where to load config from (default: ~/.puppetlabs/bolt/bolt.yaml)') do |path|
196
+ @options[:configfile] = path
197
+ end
198
+ define('--inventoryfile FILEPATH',
199
+ 'Specify where to load inventory from (default: ~/.puppetlabs/bolt/inventory.yaml)') do |path|
200
+ if ENV.include?(Bolt::Inventory::ENVIRONMENT_VAR)
201
+ raise Bolt::CLIError, "Cannot pass inventory file when #{Bolt::Inventory::ENVIRONMENT_VAR} is set"
202
+ end
203
+ @options[:inventoryfile] = path
204
+ end
205
+
206
+ separator 'Transports:'
207
+ define('--transport TRANSPORT', TRANSPORTS.keys.map(&:to_s),
208
+ "Specify a default transport: #{TRANSPORTS.keys.join(', ')}") do |t|
209
+ @options[:transport] = t
210
+ end
211
+ define('--connect-timeout TIMEOUT', Integer, 'Connection timeout (defaults vary)') do |timeout|
212
+ @options[:'connect-timeout'] = timeout
213
+ end
214
+ define('--[no-]tty', 'Request a pseudo TTY on nodes that support it') do |tty|
215
+ @options[:tty] = tty
216
+ end
217
+ define('--tmpdir DIR', 'The directory to upload and execute temporary files on the target') do |tmpdir|
218
+ @options[:tmpdir] = tmpdir
219
+ end
220
+
221
+ separator 'Display:'
222
+ define('--format FORMAT', 'Output format to use: human or json') do |format|
223
+ @options[:format] = format
224
+ end
225
+ define('--[no-]color', 'Whether to show output in color') do |color|
226
+ @options[:color] = color
227
+ end
228
+ define('-h', '--help', 'Display help') do |_|
229
+ @options[:help] = true
230
+ end
231
+ define('--verbose', 'Display verbose logging') do |_|
232
+ @options[:verbose] = true
233
+ end
234
+ define('--debug', 'Display debug logging') do |_|
235
+ @options[:debug] = true
236
+ end
237
+ define('--version', 'Display the version') do |_|
238
+ puts Bolt::VERSION
239
+ raise Bolt::CLIExit
240
+ end
241
+
242
+ update
243
+ end
244
+
245
+ def update
246
+ # show the --nodes and --query switches by default
247
+ @nodes.hide = @query.hide = false
248
+
249
+ # Update the banner according to the mode
250
+ self.banner = case @options[:mode]
251
+ when 'plan'
252
+ # don't show the --nodes and --query switches in the plan help
253
+ @nodes.hide = @query.hide = true
254
+ PLAN_HELP
255
+ when 'command'
256
+ COMMAND_HELP
257
+ when 'script'
258
+ SCRIPT_HELP
259
+ when 'task'
260
+ TASK_HELP
261
+ when 'file'
262
+ FILE_HELP
263
+ else
264
+ BANNER
265
+ end
266
+ end
267
+
268
+ def parse_params(params)
269
+ json = get_arg_input(params)
270
+ JSON.parse(json)
271
+ rescue JSON::ParserError => err
272
+ raise Bolt::CLIError, "Unable to parse --params value as JSON: #{err}"
273
+ end
274
+
275
+ def get_arg_input(value)
276
+ if value.start_with?('@')
277
+ file = value.sub(/^@/, '')
278
+ read_arg_file(file)
279
+ elsif value == '-'
280
+ STDIN.read
281
+ else
282
+ value
283
+ end
284
+ end
285
+
286
+ def read_arg_file(file)
287
+ File.read(file)
288
+ rescue StandardError => err
289
+ raise Bolt::FileError.new("Error attempting to read #{file}: #{err}", file)
290
+ end
291
+ end
292
+ end
data/lib/bolt/cli.rb CHANGED
@@ -7,6 +7,7 @@ require 'io/console'
7
7
  require 'logging'
8
8
  require 'optparse'
9
9
  require 'bolt/analytics'
10
+ require 'bolt/bolt_option_parser'
10
11
  require 'bolt/config'
11
12
  require 'bolt/error'
12
13
  require 'bolt/executor'
@@ -20,298 +21,8 @@ require 'bolt/version'
20
21
  require 'bolt/util/on_access'
21
22
 
22
23
  module Bolt
23
- class CLIError < Bolt::Error
24
- def initialize(msg)
25
- super(msg, "bolt/cli-error")
26
- end
27
- end
28
-
29
24
  class CLIExit < StandardError; end
30
-
31
25
  class CLI
32
- class BoltOptionParser < OptionParser
33
- def self.examples(cmd, desc)
34
- <<-EXAMP
35
- #{desc} a Windows host via WinRM, providing for the password
36
- bolt #{cmd} -n winrm://winhost -u Administrator -p
37
- #{desc} the local machine, a Linux host via SSH, and hosts from a group specified in an inventory file
38
- bolt #{cmd} -n localhost,nixhost,node_group
39
- #{desc} Windows hosts queried from PuppetDB via WinRM as a domain user, prompting for the password
40
- bolt #{cmd} -q 'inventory[certname] { facts.os.family = "windows" }' --transport winrm -u 'domain\\Administrator' -p
41
- EXAMP
42
- end
43
-
44
- BANNER = <<-HELP
45
- Usage: bolt <subcommand> <action> [options]
46
-
47
- Available subcommands:
48
- bolt command run <command> Run a command remotely
49
- bolt file upload <src> <dest> Upload a local file
50
- bolt script run <script> Upload a local script and run it remotely
51
- bolt task show Show list of available tasks
52
- bolt task show <task> Show documentation for task
53
- bolt task run <task> [params] Run a Puppet task
54
- bolt plan show Show list of available plans
55
- bolt plan show <plan> Show details for plan
56
- bolt plan run <plan> [params] Run a Puppet task plan
57
-
58
- Run `bolt <subcommand> --help` to view specific examples.
59
-
60
- where [options] are:
61
- HELP
62
-
63
- TASK_HELP = <<-HELP
64
- Usage: bolt task <action> <task> [options] [parameters]
65
-
66
- Available actions are:
67
- show Show list of available tasks
68
- show <task> Show documentation for task
69
- run Run a Puppet task
70
-
71
- Parameters are of the form <parameter>=<value>.
72
-
73
- #{examples('task run facts', 'run facter on')}
74
- Available options are:
75
- HELP
76
-
77
- COMMAND_HELP = <<-HELP
78
- Usage: bolt command <action> <command> [options]
79
-
80
- Available actions are:
81
- run Run a command remotely
82
-
83
- #{examples('command run hostname', 'run hostname on')}
84
- Available options are:
85
- HELP
86
-
87
- SCRIPT_HELP = <<-HELP
88
- Usage: bolt script <action> <script> [[arg1] ... [argN]] [options]
89
-
90
- Available actions are:
91
- run Upload a local script and run it remotely
92
-
93
- #{examples('script run my_script.ps1 some args', 'run a script on')}
94
- Available options are:
95
- HELP
96
-
97
- PLAN_HELP = <<-HELP
98
- Usage: bolt plan <action> <plan> [options] [parameters]
99
-
100
- Available actions are:
101
- show Show list of available plans
102
- show <plan> Show details for plan
103
- run Run a Puppet task plan
104
-
105
- Parameters are of the form <parameter>=<value>.
106
-
107
- #{examples('plan run canary command=hostname', 'run the canary plan on')}
108
- Available options are:
109
- HELP
110
-
111
- FILE_HELP = <<-HELP
112
- Usage: bolt file <action> [options]
113
-
114
- Available actions are:
115
- upload <src> <dest> Upload local file <src> to <dest> on each node
116
-
117
- #{examples('file upload /tmp/source /etc/profile.d/login.sh', 'upload a file to')}
118
- Available options are:
119
- HELP
120
-
121
- # A helper mixin for OptionParser::Switch instances which will allow
122
- # us to show/hide particular switch in the help message produced by
123
- # the OptionParser#help method on demand.
124
- module SwitchHider
125
- attr_accessor :hide
126
-
127
- def summarize(*args)
128
- return self if hide
129
- super
130
- end
131
- end
132
-
133
- def initialize(options)
134
- super()
135
-
136
- @options = options
137
-
138
- @nodes = define('-n', '--nodes NODES',
139
- 'Identifies the nodes to target.',
140
- 'Enter a comma-separated list of node URIs or group names.',
141
- "Or read a node list from an input file '@<file>' or stdin '-'.",
142
- 'Example: --nodes localhost,node_group,ssh://nix.com:23,winrm://windows.puppet.com',
143
- 'URI format is [protocol://]host[:port]',
144
- "SSH is the default protocol; may be #{TRANSPORTS.keys.join(', ')}",
145
- 'For Windows nodes, specify the winrm:// protocol if it has not be configured',
146
- 'For SSH, port defaults to `22`',
147
- 'For WinRM, port defaults to `5985` or `5986` based on the --[no-]ssl setting') do |nodes|
148
- @options[:nodes] << get_arg_input(nodes)
149
- end.extend(SwitchHider)
150
- @query = define('-q', '--query QUERY', 'Query PuppetDB to determine the targets') do |query|
151
- @options[:query] = query
152
- end.extend(SwitchHider)
153
- define('--noop', 'Execute a task that supports it in noop mode') do |_|
154
- @options[:noop] = true
155
- end
156
- define('--description DESCRIPTION',
157
- 'Description to use for the job') do |description|
158
- @options[:description] = description
159
- end
160
- define('--params PARAMETERS',
161
- "Parameters to a task or plan as json, a json file '@<file>', or on stdin '-'") do |params|
162
- @options[:task_options] = parse_params(params)
163
- end
164
-
165
- separator 'Authentication:'
166
- define('-u', '--user USER', 'User to authenticate as') do |user|
167
- @options[:user] = user
168
- end
169
- define('-p', '--password [PASSWORD]',
170
- 'Password to authenticate with. Omit the value to prompt for the password.') do |password|
171
- if password.nil?
172
- STDOUT.print "Please enter your password: "
173
- @options[:password] = STDIN.noecho(&:gets).chomp
174
- STDOUT.puts
175
- else
176
- @options[:password] = password
177
- end
178
- end
179
- define('--private-key KEY', 'Private ssh key to authenticate with') do |key|
180
- @options[:'private-key'] = key
181
- end
182
- define('--[no-]host-key-check', 'Check host keys with SSH') do |host_key_check|
183
- @options[:'host-key-check'] = host_key_check
184
- end
185
- define('--[no-]ssl', 'Use SSL with WinRM') do |ssl|
186
- @options[:ssl] = ssl
187
- end
188
- define('--[no-]ssl-verify', 'Verify remote host SSL certificate with WinRM') do |ssl_verify|
189
- @options[:'ssl-verify'] = ssl_verify
190
- end
191
-
192
- separator 'Escalation:'
193
- define('--run-as USER', 'User to run as using privilege escalation') do |user|
194
- @options[:'run-as'] = user
195
- end
196
- define('--sudo-password [PASSWORD]',
197
- 'Password for privilege escalation. Omit the value to prompt for the password.') do |password|
198
- if password.nil?
199
- STDOUT.print "Please enter your privilege escalation password: "
200
- @options[:'sudo-password'] = STDIN.noecho(&:gets).chomp
201
- STDOUT.puts
202
- else
203
- @options[:'sudo-password'] = password
204
- end
205
- end
206
-
207
- separator 'Run context:'
208
- define('-c', '--concurrency CONCURRENCY', Integer,
209
- 'Maximum number of simultaneous connections (default: 100)') do |concurrency|
210
- @options[:concurrency] = concurrency
211
- end
212
- define('--modulepath MODULES',
213
- "List of directories containing modules, separated by '#{File::PATH_SEPARATOR}'") do |modulepath|
214
- @options[:modulepath] = modulepath.split(File::PATH_SEPARATOR)
215
- end
216
- define('--configfile FILEPATH',
217
- 'Specify where to load config from (default: ~/.puppetlabs/bolt.yaml)') do |path|
218
- @options[:configfile] = path
219
- end
220
- define('--inventoryfile FILEPATH',
221
- 'Specify where to load inventory from (default: ~/.puppetlabs/bolt/inventory.yaml)') do |path|
222
- if ENV.include?(Bolt::Inventory::ENVIRONMENT_VAR)
223
- raise Bolt::CLIError, "Cannot pass inventory file when #{Bolt::Inventory::ENVIRONMENT_VAR} is set"
224
- end
225
- @options[:inventoryfile] = path
226
- end
227
-
228
- separator 'Transports:'
229
- define('--transport TRANSPORT', TRANSPORTS.keys.map(&:to_s),
230
- "Specify a default transport: #{TRANSPORTS.keys.join(', ')}") do |t|
231
- @options[:transport] = t
232
- end
233
- define('--connect-timeout TIMEOUT', Integer, 'Connection timeout (defaults vary)') do |timeout|
234
- @options[:'connect-timeout'] = timeout
235
- end
236
- define('--[no-]tty', 'Request a pseudo TTY on nodes that support it') do |tty|
237
- @options[:tty] = tty
238
- end
239
- define('--tmpdir DIR', 'The directory to upload and execute temporary files on the target') do |tmpdir|
240
- @options[:tmpdir] = tmpdir
241
- end
242
-
243
- separator 'Display:'
244
- define('--format FORMAT', 'Output format to use: human or json') do |format|
245
- @options[:format] = format
246
- end
247
- define('--[no-]color', 'Whether to show output in color') do |color|
248
- @options[:color] = color
249
- end
250
- define('-h', '--help', 'Display help') do |_|
251
- @options[:help] = true
252
- end
253
- define('--verbose', 'Display verbose logging') do |_|
254
- @options[:verbose] = true
255
- end
256
- define('--debug', 'Display debug logging') do |_|
257
- @options[:debug] = true
258
- end
259
- define('--version', 'Display the version') do |_|
260
- puts Bolt::VERSION
261
- raise Bolt::CLIExit
262
- end
263
-
264
- update
265
- end
266
-
267
- def update
268
- # show the --nodes and --query switches by default
269
- @nodes.hide = @query.hide = false
270
-
271
- # Update the banner according to the mode
272
- self.banner = case @options[:mode]
273
- when 'plan'
274
- # don't show the --nodes and --query switches in the plan help
275
- @nodes.hide = @query.hide = true
276
- PLAN_HELP
277
- when 'command'
278
- COMMAND_HELP
279
- when 'script'
280
- SCRIPT_HELP
281
- when 'task'
282
- TASK_HELP
283
- when 'file'
284
- FILE_HELP
285
- else
286
- BANNER
287
- end
288
- end
289
-
290
- def parse_params(params)
291
- json = get_arg_input(params)
292
- JSON.parse(json)
293
- rescue JSON::ParserError => err
294
- raise Bolt::CLIError, "Unable to parse --params value as JSON: #{err}"
295
- end
296
-
297
- def get_arg_input(value)
298
- if value.start_with?('@')
299
- file = value.sub(/^@/, '')
300
- read_arg_file(file)
301
- elsif value == '-'
302
- STDIN.read
303
- else
304
- value
305
- end
306
- end
307
-
308
- def read_arg_file(file)
309
- File.read(file)
310
- rescue StandardError => err
311
- raise Bolt::FileError.new("Error attempting to read #{file}: #{err}", file)
312
- end
313
- end
314
-
315
26
  COMMANDS = { 'command' => %w[run],
316
27
  'script' => %w[run],
317
28
  'task' => %w[show run],
@@ -323,11 +34,8 @@ Available options are:
323
34
  def initialize(argv)
324
35
  Bolt::Logger.initialize_logging
325
36
  @logger = Logging.logger[self]
326
-
327
37
  @config = Bolt::Config.new
328
-
329
38
  @argv = argv
330
-
331
39
  @options = {
332
40
  nodes: []
333
41
  }
@@ -365,8 +73,7 @@ Available options are:
365
73
  raise Bolt::CLIExit
366
74
  end
367
75
 
368
- config.load_file(options[:configfile])
369
- config.update_from_cli(options)
76
+ config.update(options)
370
77
  config.validate
371
78
  Bolt::Logger.configure(config)
372
79
 
@@ -503,6 +210,7 @@ Available options are:
503
210
  end
504
211
 
505
212
  @analytics.screen_view(screen,
213
+ output_format: config[:format],
506
214
  target_nodes: options.fetch(:targets, []).count,
507
215
  inventory_nodes: inventory.node_names.count,
508
216
  inventory_groups: inventory.group_names.count)
data/lib/bolt/config.rb CHANGED
@@ -68,9 +68,13 @@ module Bolt
68
68
  local: {}
69
69
  }.freeze
70
70
 
71
+ BOLTDIR_NAME = 'Boltdir'
72
+
71
73
  def initialize(**kwargs)
72
74
  super()
73
75
  @logger = Logging.logger[self]
76
+ @pwd = kwargs.delete(:pwd)
77
+
74
78
  DEFAULTS.merge(kwargs).each { |k, v| self[k] = v }
75
79
 
76
80
  # add an entry for the default console logger
@@ -99,11 +103,6 @@ module Bolt
99
103
  Bolt::Util.deep_clone(self)
100
104
  end
101
105
 
102
- def default_paths
103
- root_path = File.expand_path(File.join('~', '.puppetlabs'))
104
- [File.join(root_path, 'bolt.yaml'), File.join(root_path, 'bolt.yml')]
105
- end
106
-
107
106
  def normalize_log(target)
108
107
  return target if target == 'console'
109
108
  target = target[5..-1] if target.start_with?('file:')
@@ -146,9 +145,55 @@ module Bolt
146
145
  end
147
146
  private :update_from_file
148
147
 
149
- def load_file(path)
150
- data = Bolt::Util.read_config_file(path, default_paths, 'config')
151
- update_from_file(data) if data
148
+ def find_boltdir(dir)
149
+ path = dir
150
+ boltdir = nil
151
+ while boltdir.nil? && path && path != File.dirname(path)
152
+ maybe_boltdir = File.join(path, BOLTDIR_NAME)
153
+ boltdir = maybe_boltdir if File.directory?(maybe_boltdir)
154
+ path = File.dirname(path)
155
+ end
156
+ boltdir
157
+ end
158
+
159
+ def pwd
160
+ @pwd ||= Dir.pwd
161
+ end
162
+
163
+ def boltdir
164
+ @boltdir ||= find_boltdir(pwd) || default_boltdir
165
+ end
166
+
167
+ def default_boltdir
168
+ File.expand_path(File.join('~', '.puppetlabs', 'bolt'))
169
+ end
170
+
171
+ def default_modulepath
172
+ [File.join(boltdir, "modules")]
173
+ end
174
+
175
+ # TODO: This is deprecated in 0.21.0 and can be removed in release 0.22.0.
176
+ def legacy_conf
177
+ return @legacy_conf if defined?(@legacy_conf)
178
+ root_path = File.expand_path(File.join('~', '.puppetlabs'))
179
+ legacy_paths = [File.join(root_path, 'bolt.yaml'), File.join(root_path, 'bolt.yml')]
180
+ @legacy_conf = legacy_paths.find { |path| File.exist?(path) }
181
+ @legacy_conf ||= legacy_paths[0]
182
+ if @legacy_conf
183
+ correct_path = File.join(default_boltdir, 'bolt.yaml')
184
+ msg = "Found configfile at deprecated location #{@legacy_conf}. Global config should be in #{correct_path}"
185
+ @logger.warn(msg)
186
+ end
187
+ @legacy_conf
188
+ end
189
+
190
+ def default_config
191
+ path = File.join(boltdir, 'bolt.yaml')
192
+ File.exist?(path) ? path : legacy_conf
193
+ end
194
+
195
+ def default_inventory
196
+ File.join(boltdir, 'inventory.yaml')
152
197
  end
153
198
 
154
199
  def update_from_cli(options)
@@ -184,6 +229,26 @@ module Bolt
184
229
  end
185
230
  end
186
231
 
232
+ # Defaults that do not vary based on boltdir should not be included here.
233
+ #
234
+ # Defaults which are treated differently from specified values like
235
+ # 'inventoryfile' cannot be included here or they will not be handled correctly.
236
+ def update_from_defaults
237
+ self[:modulepath] = default_modulepath
238
+ end
239
+
240
+ # The order in which config is processed is important
241
+ def update(options)
242
+ update_from_defaults
243
+ load_file(options[:configfile])
244
+ update_from_cli(options)
245
+ end
246
+
247
+ def load_file(path)
248
+ data = Bolt::Util.read_config_file(path, [default_config], 'config')
249
+ update_from_file(data) if data
250
+ end
251
+
187
252
  def update_from_inventory(data)
188
253
  update_from_file(data)
189
254
 
data/lib/bolt/error.rb CHANGED
@@ -41,6 +41,12 @@ module Bolt
41
41
  end
42
42
  end
43
43
 
44
+ class CLIError < Bolt::Error
45
+ def initialize(msg)
46
+ super(msg, "bolt/cli-error")
47
+ end
48
+ end
49
+
44
50
  class RunFailure < Error
45
51
  attr_reader :result_set
46
52
 
@@ -36,10 +36,6 @@ module Bolt
36
36
  end
37
37
  end
38
38
 
39
- def self.default_paths
40
- [File.expand_path(File.join('~', '.puppetlabs', 'bolt', 'inventory.yaml'))]
41
- end
42
-
43
39
  def self.from_config(config)
44
40
  if ENV.include?(ENVIRONMENT_VAR)
45
41
  begin
@@ -48,7 +44,7 @@ module Bolt
48
44
  raise Bolt::Error.new("Could not parse inventory from $#{ENVIRONMENT_VAR}", 'bolt/parse-error')
49
45
  end
50
46
  else
51
- data = Bolt::Util.read_config_file(config[:inventoryfile], default_paths, 'inventory')
47
+ data = Bolt::Util.read_config_file(config[:inventoryfile], [config.default_inventory], 'inventory')
52
48
  end
53
49
 
54
50
  inventory = new(data, config)
@@ -117,7 +117,7 @@ module Bolt
117
117
  # Building lots of strings...
118
118
  pretty_params = +""
119
119
  task_info = +""
120
- usage = +"bolt task run --nodes, -n <node-name> #{task['name']}"
120
+ usage = +"bolt task run --nodes <node-name> #{task['name']}"
121
121
 
122
122
  if task['parameters']
123
123
  replace_data_type(task['parameters'])
@@ -87,7 +87,7 @@ module Bolt
87
87
 
88
88
  def batch_command(targets, command, options = {}, &callback)
89
89
  params = {
90
- command: command
90
+ 'command' => command
91
91
  }
92
92
  results = run_task_job(targets,
93
93
  BOLT_COMMAND_TASK,
@@ -105,8 +105,8 @@ module Bolt
105
105
  content = File.open(script, &:read)
106
106
  content = Base64.encode64(content)
107
107
  params = {
108
- content: content,
109
- arguments: arguments
108
+ 'content' => content,
109
+ 'arguments' => arguments
110
110
  }
111
111
  callback ||= proc {}
112
112
  results = run_task_job(targets, BOLT_SCRIPT_TASK, params, options, &callback)
@@ -121,9 +121,9 @@ module Bolt
121
121
  content = Base64.encode64(content)
122
122
  mode = File.stat(source).mode
123
123
  params = {
124
- path: destination,
125
- content: content,
126
- mode: mode
124
+ 'path' => destination,
125
+ 'content' => content,
126
+ 'mode' => mode
127
127
  }
128
128
  callback ||= proc {}
129
129
  results = run_task_job(targets, BOLT_UPLOAD_TASK, params, options, &callback)
@@ -62,7 +62,7 @@ module Bolt
62
62
  body = { task: task.name,
63
63
  environment: @environment,
64
64
  noop: arguments['_noop'],
65
- params: arguments.reject { |k, _| k == '_noop' },
65
+ params: arguments.reject { |k, _| k.start_with?('_') },
66
66
  scope: {
67
67
  nodes: targets.map(&:host)
68
68
  } }
data/lib/bolt/util.rb CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Bolt
4
4
  module Util
5
- # CODEREVIEW I hate mixing in random modules
6
5
  class << self
7
6
  def read_config_file(path, default_paths = nil, file_name = 'file')
8
7
  logger = Logging.logger[self]
@@ -17,10 +16,16 @@ module Bolt
17
16
  end
18
17
 
19
18
  path = File.expand_path(path)
20
- File.open(path, "r:UTF-8") { |f| YAML.safe_load(f.read) }
19
+ content = File.open(path, "r:UTF-8") { |f| YAML.safe_load(f.read) }
20
+ logger.debug("Loaded #{file_name} from #{path}")
21
+ content
21
22
  rescue Errno::ENOENT
23
+ msg = "Could not read #{file_name} file: #{path}"
22
24
  if path_passed
23
- raise Bolt::FileError.new("Could not read #{file_name} file: #{path}", path)
25
+ raise Bolt::FileError.new(msg, path)
26
+ else
27
+ logger.debug(msg)
28
+ nil
24
29
  end
25
30
  rescue Psych::Exception
26
31
  raise Bolt::FileError.new("Could not parse #{file_name} file: #{path}", path)
data/lib/bolt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '0.20.7'
4
+ VERSION = '0.21.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.7
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-06-14 00:00:00.000000000 Z
11
+ date: 2018-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -351,6 +351,7 @@ files:
351
351
  - exe/bolt-inventory-pdb
352
352
  - lib/bolt.rb
353
353
  - lib/bolt/analytics.rb
354
+ - lib/bolt/bolt_option_parser.rb
354
355
  - lib/bolt/cli.rb
355
356
  - lib/bolt/config.rb
356
357
  - lib/bolt/error.rb
@@ -1634,7 +1635,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1634
1635
  version: '0'
1635
1636
  requirements: []
1636
1637
  rubyforge_project:
1637
- rubygems_version: 2.5.1
1638
+ rubygems_version: 2.6.14
1638
1639
  signing_key:
1639
1640
  specification_version: 4
1640
1641
  summary: Execute commands remotely over SSH and WinRM