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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +5 -0
  3. data/lib/morpheus/api/api_client.rb +15 -30
  4. data/lib/morpheus/api/app_templates_interface.rb +34 -7
  5. data/lib/morpheus/api/apps_interface.rb +20 -1
  6. data/lib/morpheus/api/archive_buckets_interface.rb +124 -0
  7. data/lib/morpheus/api/archive_files_interface.rb +182 -0
  8. data/lib/morpheus/api/{network_pools_interface.rb → image_builder_boot_scripts_interface.rb} +6 -6
  9. data/lib/morpheus/api/{policies_interface.rb → image_builder_image_builds_interface.rb} +20 -15
  10. data/lib/morpheus/api/image_builder_interface.rb +26 -0
  11. data/lib/morpheus/api/{network_proxies_interface.rb → image_builder_preseed_scripts_interface.rb} +6 -6
  12. data/lib/morpheus/cli.rb +10 -9
  13. data/lib/morpheus/cli/alias_command.rb +10 -9
  14. data/lib/morpheus/cli/app_templates.rb +1566 -457
  15. data/lib/morpheus/cli/apps.rb +284 -108
  16. data/lib/morpheus/cli/archives_command.rb +2184 -0
  17. data/lib/morpheus/cli/boot_scripts_command.rb +382 -0
  18. data/lib/morpheus/cli/cli_command.rb +9 -35
  19. data/lib/morpheus/cli/error_handler.rb +2 -0
  20. data/lib/morpheus/cli/hosts.rb +15 -3
  21. data/lib/morpheus/cli/image_builder_command.rb +1208 -0
  22. data/lib/morpheus/cli/instances.rb +118 -47
  23. data/lib/morpheus/cli/man_command.rb +27 -24
  24. data/lib/morpheus/cli/mixins/print_helper.rb +19 -5
  25. data/lib/morpheus/cli/mixins/provisioning_helper.rb +20 -20
  26. data/lib/morpheus/cli/option_types.rb +45 -14
  27. data/lib/morpheus/cli/preseed_scripts_command.rb +381 -0
  28. data/lib/morpheus/cli/remote.rb +1 -0
  29. data/lib/morpheus/cli/roles.rb +2 -2
  30. data/lib/morpheus/cli/shell.rb +3 -2
  31. data/lib/morpheus/cli/version.rb +1 -1
  32. data/lib/morpheus/ext/hash.rb +22 -0
  33. data/lib/morpheus/formatters.rb +33 -0
  34. data/lib/morpheus/terminal.rb +1 -1
  35. metadata +13 -21
  36. data/lib/morpheus/api/cloud_policies_interface.rb +0 -47
  37. data/lib/morpheus/api/group_policies_interface.rb +0 -47
  38. data/lib/morpheus/api/network_domains_interface.rb +0 -47
  39. data/lib/morpheus/api/network_groups_interface.rb +0 -47
  40. data/lib/morpheus/api/network_pool_servers_interface.rb +0 -47
  41. data/lib/morpheus/api/network_services_interface.rb +0 -47
  42. data/lib/morpheus/api/networks_interface.rb +0 -54
  43. data/lib/morpheus/cli/network_domains_command.rb +0 -571
  44. data/lib/morpheus/cli/network_groups_command.rb +0 -602
  45. data/lib/morpheus/cli/network_pool_servers_command.rb +0 -430
  46. data/lib/morpheus/cli/network_pools_command.rb +0 -495
  47. data/lib/morpheus/cli/network_proxies_command.rb +0 -594
  48. data/lib/morpheus/cli/network_services_command.rb +0 -148
  49. data/lib/morpheus/cli/networks_command.rb +0 -855
  50. data/lib/morpheus/cli/policies_command.rb +0 -847
  51. 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 -O field=\"value\"" ) do |option|
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 :payload
228
- opts.on('--payload JSON', String, "Payload JSON, skip all prompting") do |val|
229
- begin
230
- options[:payload] = JSON.parse(val.to_s)
231
- rescue => ex
232
- raise ::OptionParser::InvalidOption.new("Failed to parse payload as JSON. Error: #{ex.message}")
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', "Quote values for CSV Output. Default: false") do
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