morpheus-cli 2.12.5 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +5 -0
- data/lib/morpheus/api/api_client.rb +15 -30
- data/lib/morpheus/api/app_templates_interface.rb +34 -7
- data/lib/morpheus/api/apps_interface.rb +20 -1
- data/lib/morpheus/api/archive_buckets_interface.rb +124 -0
- data/lib/morpheus/api/archive_files_interface.rb +182 -0
- data/lib/morpheus/api/{network_pools_interface.rb → image_builder_boot_scripts_interface.rb} +6 -6
- data/lib/morpheus/api/{policies_interface.rb → image_builder_image_builds_interface.rb} +20 -15
- data/lib/morpheus/api/image_builder_interface.rb +26 -0
- data/lib/morpheus/api/{network_proxies_interface.rb → image_builder_preseed_scripts_interface.rb} +6 -6
- data/lib/morpheus/cli.rb +10 -9
- data/lib/morpheus/cli/alias_command.rb +10 -9
- data/lib/morpheus/cli/app_templates.rb +1566 -457
- data/lib/morpheus/cli/apps.rb +284 -108
- data/lib/morpheus/cli/archives_command.rb +2184 -0
- data/lib/morpheus/cli/boot_scripts_command.rb +382 -0
- data/lib/morpheus/cli/cli_command.rb +9 -35
- data/lib/morpheus/cli/error_handler.rb +2 -0
- data/lib/morpheus/cli/hosts.rb +15 -3
- data/lib/morpheus/cli/image_builder_command.rb +1208 -0
- data/lib/morpheus/cli/instances.rb +118 -47
- data/lib/morpheus/cli/man_command.rb +27 -24
- data/lib/morpheus/cli/mixins/print_helper.rb +19 -5
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +20 -20
- data/lib/morpheus/cli/option_types.rb +45 -14
- data/lib/morpheus/cli/preseed_scripts_command.rb +381 -0
- data/lib/morpheus/cli/remote.rb +1 -0
- data/lib/morpheus/cli/roles.rb +2 -2
- data/lib/morpheus/cli/shell.rb +3 -2
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/ext/hash.rb +22 -0
- data/lib/morpheus/formatters.rb +33 -0
- data/lib/morpheus/terminal.rb +1 -1
- metadata +13 -21
- data/lib/morpheus/api/cloud_policies_interface.rb +0 -47
- data/lib/morpheus/api/group_policies_interface.rb +0 -47
- data/lib/morpheus/api/network_domains_interface.rb +0 -47
- data/lib/morpheus/api/network_groups_interface.rb +0 -47
- data/lib/morpheus/api/network_pool_servers_interface.rb +0 -47
- data/lib/morpheus/api/network_services_interface.rb +0 -47
- data/lib/morpheus/api/networks_interface.rb +0 -54
- data/lib/morpheus/cli/network_domains_command.rb +0 -571
- data/lib/morpheus/cli/network_groups_command.rb +0 -602
- data/lib/morpheus/cli/network_pool_servers_command.rb +0 -430
- data/lib/morpheus/cli/network_pools_command.rb +0 -495
- data/lib/morpheus/cli/network_proxies_command.rb +0 -594
- data/lib/morpheus/cli/network_services_command.rb +0 -148
- data/lib/morpheus/cli/networks_command.rb +0 -855
- data/lib/morpheus/cli/policies_command.rb +0 -847
- data/scripts/generate_morpheus_commands_help.morpheus +0 -1313
@@ -0,0 +1,382 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'optparse'
|
3
|
+
require 'filesize'
|
4
|
+
require 'table_print'
|
5
|
+
require 'morpheus/cli/cli_command'
|
6
|
+
|
7
|
+
class Morpheus::Cli::BootScriptsCommand
|
8
|
+
include Morpheus::Cli::CliCommand
|
9
|
+
|
10
|
+
#set_command_name :'boot-scripts'
|
11
|
+
|
12
|
+
# lives under image-builder domain right now
|
13
|
+
set_command_hidden
|
14
|
+
def command_name
|
15
|
+
"image-builder boot-scripts"
|
16
|
+
end
|
17
|
+
|
18
|
+
register_subcommands :list, :get, :add, :update, :remove
|
19
|
+
|
20
|
+
# set_default_subcommand :list
|
21
|
+
|
22
|
+
def initialize()
|
23
|
+
# @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
|
24
|
+
end
|
25
|
+
|
26
|
+
def connect(opts)
|
27
|
+
@api_client = establish_remote_appliance_connection(opts)
|
28
|
+
@image_builder_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).image_builder
|
29
|
+
@boot_scripts_interface = @image_builder_interface.boot_scripts
|
30
|
+
end
|
31
|
+
|
32
|
+
def handle(args)
|
33
|
+
handle_subcommand(args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def list(args)
|
37
|
+
options = {}
|
38
|
+
optparse = OptionParser.new do|opts|
|
39
|
+
opts.banner = subcommand_usage()
|
40
|
+
build_common_options(opts, options, [:list, :json, :dry_run])
|
41
|
+
end
|
42
|
+
optparse.parse!(args)
|
43
|
+
connect(options)
|
44
|
+
begin
|
45
|
+
params = {}
|
46
|
+
[:phrase, :offset, :max, :sort, :direction].each do |k|
|
47
|
+
params[k] = options[k] unless options[k].nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
if options[:dry_run]
|
51
|
+
print_dry_run @boot_scripts_interface.dry.list(params)
|
52
|
+
return
|
53
|
+
end
|
54
|
+
|
55
|
+
json_response = @boot_scripts_interface.list(params)
|
56
|
+
if options[:json]
|
57
|
+
print JSON.pretty_generate(json_response)
|
58
|
+
print "\n"
|
59
|
+
return
|
60
|
+
end
|
61
|
+
boot_scripts = json_response['bootScripts']
|
62
|
+
title = "Morpheus Boot Scripts"
|
63
|
+
subtitles = []
|
64
|
+
if params[:phrase]
|
65
|
+
subtitles << "Search: #{params[:phrase]}".strip
|
66
|
+
end
|
67
|
+
print_h1 title, subtitles
|
68
|
+
if boot_scripts.empty?
|
69
|
+
print cyan,"No boot scripts found.",reset,"\n"
|
70
|
+
else
|
71
|
+
rows = boot_scripts.collect {|boot_script|
|
72
|
+
row = {
|
73
|
+
id: boot_script['id'],
|
74
|
+
name: boot_script['fileName']
|
75
|
+
}
|
76
|
+
row
|
77
|
+
}
|
78
|
+
columns = [:id, :name]
|
79
|
+
if options[:include_fields]
|
80
|
+
columns = options[:include_fields]
|
81
|
+
end
|
82
|
+
print cyan
|
83
|
+
print as_pretty_table(rows, columns, options)
|
84
|
+
print reset
|
85
|
+
print_results_pagination(json_response)
|
86
|
+
end
|
87
|
+
print reset,"\n"
|
88
|
+
rescue RestClient::Exception => e
|
89
|
+
print_rest_exception(e, options)
|
90
|
+
exit 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def get(args)
|
95
|
+
options = {}
|
96
|
+
optparse = OptionParser.new do|opts|
|
97
|
+
opts.banner = subcommand_usage("[boot-script]")
|
98
|
+
build_common_options(opts, options, [:json, :dry_run])
|
99
|
+
end
|
100
|
+
optparse.parse!(args)
|
101
|
+
if args.count < 1
|
102
|
+
print_error Morpheus::Terminal.angry_prompt
|
103
|
+
puts_error "#{command_name} missing argument: [boot-script]\n#{optparse}"
|
104
|
+
return 1
|
105
|
+
end
|
106
|
+
connect(options)
|
107
|
+
begin
|
108
|
+
if options[:dry_run]
|
109
|
+
if args[0].to_s =~ /\A\d{1,}\Z/
|
110
|
+
print_dry_run @boot_scripts_interface.dry.get(args[0].to_i)
|
111
|
+
else
|
112
|
+
print_dry_run @boot_scripts_interface.dry.list({name:args[0]})
|
113
|
+
end
|
114
|
+
return
|
115
|
+
end
|
116
|
+
boot_script = find_boot_script_by_name_or_id(args[0])
|
117
|
+
return 1 if boot_script.nil?
|
118
|
+
json_response = {'bootScript' => boot_script} # skip redundant request
|
119
|
+
# json_response = @boot_scripts_interface.get(boot_script['id'])
|
120
|
+
boot_script = json_response['bootScript']
|
121
|
+
if options[:json]
|
122
|
+
print JSON.pretty_generate(json_response)
|
123
|
+
return
|
124
|
+
end
|
125
|
+
print_h1 "Boot Script Details"
|
126
|
+
print cyan
|
127
|
+
description_cols = {
|
128
|
+
"ID" => 'id',
|
129
|
+
"Name" => 'fileName',
|
130
|
+
# "Description" => 'description',
|
131
|
+
# "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
132
|
+
# "Visibility" => lambda {|it| it['visibility'] ? it['visibility'].capitalize() : 'Private' },
|
133
|
+
}
|
134
|
+
print_description_list(description_cols, boot_script)
|
135
|
+
|
136
|
+
print_h2 "Script"
|
137
|
+
print cyan
|
138
|
+
puts boot_script['content']
|
139
|
+
|
140
|
+
print reset,"\n"
|
141
|
+
|
142
|
+
rescue RestClient::Exception => e
|
143
|
+
print_rest_exception(e, options)
|
144
|
+
return 1
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def add(args)
|
149
|
+
options = {}
|
150
|
+
optparse = OptionParser.new do|opts|
|
151
|
+
opts.banner = subcommand_usage("[fileName]")
|
152
|
+
build_option_type_options(opts, options, add_boot_script_option_types(false))
|
153
|
+
build_common_options(opts, options, [:options, :json, :dry_run, :quiet])
|
154
|
+
end
|
155
|
+
optparse.parse!(args)
|
156
|
+
connect(options)
|
157
|
+
begin
|
158
|
+
options[:options] ||= {}
|
159
|
+
# support [boot-script] as first argument still
|
160
|
+
if args[0]
|
161
|
+
options[:options]['fileName'] = args[0]
|
162
|
+
end
|
163
|
+
|
164
|
+
payload = {
|
165
|
+
'bootScript' => {}
|
166
|
+
}
|
167
|
+
# prompt for Script Content unless --file is passed.
|
168
|
+
my_options = add_boot_script_option_types()
|
169
|
+
if options[:options]['file']
|
170
|
+
my_options = my_options.reject {|it| it['fieldName'] == 'content' }
|
171
|
+
# elsif options[:options]['content']
|
172
|
+
# my_options = my_options.reject {|it| it['fieldName'] == 'file' }
|
173
|
+
else
|
174
|
+
my_options = my_options.reject {|it| it['fieldName'] == 'file' }
|
175
|
+
end
|
176
|
+
params = Morpheus::Cli::OptionTypes.prompt(my_options, options[:options], @api_client, options[:params])
|
177
|
+
script_file = params.delete('file')
|
178
|
+
if script_file
|
179
|
+
if !File.exists?(script_file)
|
180
|
+
print_red_alert "File not found: #{script_file}"
|
181
|
+
return 1
|
182
|
+
end
|
183
|
+
payload['bootScript']['content'] = File.read(script_file)
|
184
|
+
end
|
185
|
+
payload['bootScript'].merge!(params)
|
186
|
+
if options[:dry_run]
|
187
|
+
print_dry_run @boot_scripts_interface.dry.create(payload)
|
188
|
+
return
|
189
|
+
end
|
190
|
+
json_response = @boot_scripts_interface.create(payload)
|
191
|
+
if options[:json]
|
192
|
+
print JSON.pretty_generate(json_response)
|
193
|
+
print "\n"
|
194
|
+
elsif !options[:quiet]
|
195
|
+
print_green_success "Added image build #{payload['bootScript']['fileName']}"
|
196
|
+
# list([])
|
197
|
+
boot_script = json_response['bootScript']
|
198
|
+
get([boot_script['id']])
|
199
|
+
end
|
200
|
+
|
201
|
+
rescue RestClient::Exception => e
|
202
|
+
print_rest_exception(e, options)
|
203
|
+
exit 1
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def update(args)
|
208
|
+
options = {}
|
209
|
+
optparse = OptionParser.new do|opts|
|
210
|
+
opts.banner = subcommand_usage("[boot-script] [options]")
|
211
|
+
build_option_type_options(opts, options, update_boot_script_option_types(false))
|
212
|
+
build_common_options(opts, options, [:options, :json, :dry_run])
|
213
|
+
end
|
214
|
+
optparse.parse!(args)
|
215
|
+
if args.count < 1
|
216
|
+
puts optparse
|
217
|
+
return 1
|
218
|
+
end
|
219
|
+
connect(options)
|
220
|
+
|
221
|
+
begin
|
222
|
+
boot_script = find_boot_script_by_name_or_id(args[0])
|
223
|
+
|
224
|
+
payload = {
|
225
|
+
'bootScript' => {id: boot_script["id"]}
|
226
|
+
}
|
227
|
+
|
228
|
+
params = options[:options] || {}
|
229
|
+
#puts "parsed params is : #{params.inspect}"
|
230
|
+
params = params.select {|k,v| params[k].to_s != "" }
|
231
|
+
if params.empty?
|
232
|
+
print_red_alert "Specify atleast one option to update"
|
233
|
+
puts optparse
|
234
|
+
return 1
|
235
|
+
end
|
236
|
+
|
237
|
+
# prompt for Script Content unless --file is passed.
|
238
|
+
# my_options = add_boot_script_option_types()
|
239
|
+
# if options[:options]['file']
|
240
|
+
# my_options = my_options.reject {|it| it['fieldName'] == 'content' }
|
241
|
+
# # elsif options[:options]['content']
|
242
|
+
# # my_options = my_options.reject {|it| it['fieldName'] == 'file' }
|
243
|
+
# else
|
244
|
+
# my_options = my_options.reject {|it| it['fieldName'] == 'file' }
|
245
|
+
# end
|
246
|
+
# params = Morpheus::Cli::OptionTypes.prompt(my_options, options[:options], @api_client, options[:params])
|
247
|
+
script_file = params.delete('file')
|
248
|
+
if script_file
|
249
|
+
if !File.exists?(script_file)
|
250
|
+
print_red_alert "File not found: #{script_file}"
|
251
|
+
return 1
|
252
|
+
end
|
253
|
+
payload['bootScript']['content'] = File.read(script_file)
|
254
|
+
end
|
255
|
+
payload['bootScript'].merge!(params)
|
256
|
+
|
257
|
+
if options[:dry_run]
|
258
|
+
print_dry_run @boot_scripts_interface.dry.update(boot_script["id"], payload)
|
259
|
+
return
|
260
|
+
end
|
261
|
+
|
262
|
+
json_response = @boot_scripts_interface.update(boot_script["id"], payload)
|
263
|
+
if options[:json]
|
264
|
+
print JSON.pretty_generate(json_response)
|
265
|
+
print "\n"
|
266
|
+
else
|
267
|
+
print_green_success "Updated boot script #{boot_script['fileName']}"
|
268
|
+
get([boot_script['id']])
|
269
|
+
end
|
270
|
+
return 0
|
271
|
+
rescue RestClient::Exception => e
|
272
|
+
print_rest_exception(e, options)
|
273
|
+
return 1
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def remove(args)
|
278
|
+
options = {}
|
279
|
+
optparse = OptionParser.new do|opts|
|
280
|
+
opts.banner = subcommand_usage("[boot-script]")
|
281
|
+
build_common_options(opts, options, [:account, :auto_confirm, :json, :dry_run])
|
282
|
+
end
|
283
|
+
optparse.parse!(args)
|
284
|
+
|
285
|
+
if args.count < 1
|
286
|
+
print_error Morpheus::Terminal.angry_prompt
|
287
|
+
puts_error "#{command_name} missing argument: [boot-script]\n#{optparse}"
|
288
|
+
return 1
|
289
|
+
end
|
290
|
+
|
291
|
+
connect(options)
|
292
|
+
begin
|
293
|
+
boot_script = find_boot_script_by_name_or_id(args[0])
|
294
|
+
return 1 if boot_script.nil?
|
295
|
+
|
296
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the boot script: #{boot_script['fileName']}?")
|
297
|
+
return 9, "aborted command"
|
298
|
+
end
|
299
|
+
if options[:dry_run]
|
300
|
+
print_dry_run @boot_scripts_interface.dry.destroy(boot_script['id'])
|
301
|
+
return 0
|
302
|
+
end
|
303
|
+
json_response = @boot_scripts_interface.destroy(boot_script['id'])
|
304
|
+
if options[:json]
|
305
|
+
print JSON.pretty_generate(json_response)
|
306
|
+
print "\n"
|
307
|
+
else
|
308
|
+
print_green_success "Removed boot script #{boot_script['fileName']}"
|
309
|
+
# list([])
|
310
|
+
end
|
311
|
+
return 0
|
312
|
+
rescue RestClient::Exception => e
|
313
|
+
print_rest_exception(e, options)
|
314
|
+
return 1
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
private
|
319
|
+
|
320
|
+
def get_available_boot_script_types()
|
321
|
+
[{'name' => 'VMware', 'value' => 'vmware'}]
|
322
|
+
end
|
323
|
+
|
324
|
+
def add_boot_script_option_types(connected=true)
|
325
|
+
[
|
326
|
+
{'fieldName' => 'fileName', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Enter a name for this script.'},
|
327
|
+
# {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false},
|
328
|
+
{'fieldName' => 'file', 'fieldLabel' => 'Script File', 'type' => 'file', 'required' => false, 'description' => 'Set script contents to that of a local file.'},
|
329
|
+
{'fieldName' => 'content', 'fieldLabel' => 'Script', 'type' => 'code-editor', 'required' => true},
|
330
|
+
]
|
331
|
+
end
|
332
|
+
|
333
|
+
def update_boot_script_option_types(connected=true)
|
334
|
+
list = add_boot_script_option_types(connected)
|
335
|
+
# list = list.reject {|it| ["group"].include? it['fieldName'] }
|
336
|
+
list.each {|it| it['required'] = false }
|
337
|
+
list
|
338
|
+
end
|
339
|
+
|
340
|
+
def find_boot_script_by_name_or_id(val)
|
341
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
342
|
+
return find_boot_script_by_id(val)
|
343
|
+
else
|
344
|
+
return find_boot_script_by_name(val)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def find_boot_script_by_id(id)
|
349
|
+
begin
|
350
|
+
json_response = @boot_scripts_interface.get(id.to_i)
|
351
|
+
return json_response['bootScript']
|
352
|
+
rescue RestClient::Exception => e
|
353
|
+
if e.response && e.response.code == 404
|
354
|
+
print_red_alert "Boot Script not found by id #{id}"
|
355
|
+
return nil
|
356
|
+
else
|
357
|
+
raise e
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def find_boot_script_by_name(name)
|
363
|
+
boot_scripts = @boot_scripts_interface.list({name: name.to_s})['bootScripts']
|
364
|
+
if boot_scripts.empty?
|
365
|
+
print_red_alert "Boot Script not found by name #{name}"
|
366
|
+
return nil
|
367
|
+
elsif boot_scripts.size > 1
|
368
|
+
print_red_alert "#{boot_scripts.size} boot scripts found by name #{name}"
|
369
|
+
# print_boot_scripts_table(boot_scripts, {color: red})
|
370
|
+
rows = boot_scripts.collect do |boot_script|
|
371
|
+
{id: it['id'], name: it['fileName']}
|
372
|
+
end
|
373
|
+
print red
|
374
|
+
tp rows, [:id, :name]
|
375
|
+
print reset,"\n"
|
376
|
+
return nil
|
377
|
+
else
|
378
|
+
return boot_scripts[0]
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
require 'json'
|
3
1
|
require 'morpheus/logging'
|
4
2
|
require 'morpheus/cli/option_parser'
|
5
3
|
require 'morpheus/cli/cli_registry'
|
@@ -196,7 +194,7 @@ module Morpheus
|
|
196
194
|
|
197
195
|
when :options
|
198
196
|
options[:options] ||= {}
|
199
|
-
opts.on( '-O', '--option OPTION', "Option in the format
|
197
|
+
opts.on( '-O', '--option OPTION', "Option in the format var=\"value\"" ) do |option|
|
200
198
|
# todo: look ahead and parse ALL the option=value args after -O switch
|
201
199
|
#custom_option_args = option.split('=')
|
202
200
|
custom_option_args = option.sub(/\s?\=\s?/, '__OPTION_DELIM__').split('__OPTION_DELIM__')
|
@@ -224,38 +222,14 @@ module Morpheus
|
|
224
222
|
options[:options][:no_prompt] = true
|
225
223
|
end
|
226
224
|
|
227
|
-
when :
|
228
|
-
opts.on('--
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
end
|
234
|
-
end
|
235
|
-
opts.on('--payload-yaml YAML', String, "Payload YAML, skip all prompting") do |val|
|
236
|
-
begin
|
237
|
-
options[:payload] = YAML.load(val.to_s)
|
238
|
-
rescue => ex
|
239
|
-
raise ::OptionParser::InvalidOption.new("Failed to parse payload as YAML. Error: #{ex.message}")
|
240
|
-
end
|
241
|
-
end
|
242
|
-
opts.on('--payload-file FILE', String, "Payload from a local JSON or YAML file, skip all prompting") do |val|
|
243
|
-
options[:payload_file] = val.to_s
|
244
|
-
begin
|
245
|
-
payload_file = File.expand_path(options[:payload_file])
|
246
|
-
if !File.exists?(payload_file) || !File.file?(payload_file)
|
247
|
-
raise ::OptionParser::InvalidOption.new("File not found: #{payload_file}")
|
248
|
-
#return false
|
249
|
-
end
|
250
|
-
if payload_file =~ /\.ya?ml\Z/
|
251
|
-
option[:payload] = YAML.load_file(payload_file)
|
252
|
-
else
|
253
|
-
option[:payload] = JSON.parse(File.read(payload_file))
|
254
|
-
end
|
255
|
-
rescue => ex
|
256
|
-
raise ::OptionParser::InvalidOption.new("Failed to parse payload file: #{payload_file} Error: #{ex.message}")
|
257
|
-
end
|
225
|
+
when :noprompt
|
226
|
+
opts.on('-N','--no-prompt', "Skip prompts. Use default values for all optional fields.") do |val|
|
227
|
+
options[:no_prompt] = true
|
228
|
+
# ew, stored in here for now because options[:options] is what is passed into OptionTypes.prompt() everywhere!
|
229
|
+
options[:options] ||= {}
|
230
|
+
options[:options][:no_prompt] = true
|
258
231
|
end
|
232
|
+
|
259
233
|
when :list
|
260
234
|
opts.on( '-m', '--max MAX', "Max Results" ) do |max|
|
261
235
|
options[:max] = max.to_i
|
@@ -377,7 +351,7 @@ module Morpheus
|
|
377
351
|
end
|
378
352
|
end
|
379
353
|
|
380
|
-
opts.on(nil, '--csv-quotes', "
|
354
|
+
opts.on(nil, '--csv-quotes', "Wrap CSV values with \". Default: false") do
|
381
355
|
options[:csv] = true
|
382
356
|
options[:format] = :csv
|
383
357
|
options[:csv_quotes] = true
|