morpheus-cli 2.12.5 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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