morpheus-cli 3.5.2 → 3.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/morpheus/api/api_client.rb +16 -0
  3. data/lib/morpheus/api/blueprints_interface.rb +84 -0
  4. data/lib/morpheus/api/execution_request_interface.rb +33 -0
  5. data/lib/morpheus/api/instances_interface.rb +21 -0
  6. data/lib/morpheus/api/packages_interface.rb +25 -5
  7. data/lib/morpheus/api/processes_interface.rb +34 -0
  8. data/lib/morpheus/api/roles_interface.rb +7 -0
  9. data/lib/morpheus/api/servers_interface.rb +8 -0
  10. data/lib/morpheus/api/user_settings_interface.rb +76 -0
  11. data/lib/morpheus/cli.rb +5 -1
  12. data/lib/morpheus/cli/alias_command.rb +1 -1
  13. data/lib/morpheus/cli/app_templates.rb +2 -1
  14. data/lib/morpheus/cli/apps.rb +173 -19
  15. data/lib/morpheus/cli/blueprints_command.rb +2134 -0
  16. data/lib/morpheus/cli/cli_command.rb +3 -1
  17. data/lib/morpheus/cli/clouds.rb +4 -10
  18. data/lib/morpheus/cli/coloring_command.rb +14 -8
  19. data/lib/morpheus/cli/containers_command.rb +92 -5
  20. data/lib/morpheus/cli/execution_request_command.rb +313 -0
  21. data/lib/morpheus/cli/hosts.rb +188 -7
  22. data/lib/morpheus/cli/instances.rb +472 -9
  23. data/lib/morpheus/cli/login.rb +1 -1
  24. data/lib/morpheus/cli/mixins/print_helper.rb +8 -0
  25. data/lib/morpheus/cli/mixins/processes_helper.rb +134 -0
  26. data/lib/morpheus/cli/option_types.rb +21 -16
  27. data/lib/morpheus/cli/packages_command.rb +469 -17
  28. data/lib/morpheus/cli/processes_command.rb +313 -0
  29. data/lib/morpheus/cli/remote.rb +20 -9
  30. data/lib/morpheus/cli/roles.rb +186 -6
  31. data/lib/morpheus/cli/shell.rb +10 -1
  32. data/lib/morpheus/cli/tasks.rb +4 -1
  33. data/lib/morpheus/cli/user_settings_command.rb +431 -0
  34. data/lib/morpheus/cli/version.rb +1 -1
  35. data/lib/morpheus/cli/whoami.rb +1 -1
  36. data/lib/morpheus/formatters.rb +14 -0
  37. data/lib/morpheus/morpkg.rb +119 -0
  38. data/morpheus-cli.gemspec +1 -0
  39. metadata +26 -2
@@ -87,7 +87,7 @@ class Morpheus::Cli::Login
87
87
 
88
88
  if creds
89
89
  if !options[:quiet]
90
- print green,"Logged in to #{@appliance_name} as #{::Morpheus::Cli::Remote.load_active_remote()[:username]}#{reset}", reset, "\n"
90
+ print green,"Logged in to #{@appliance_name} as #{::Morpheus::Cli::Remote.load_remote(@appliance_name)[:username]}#{reset}", reset, "\n"
91
91
  end
92
92
  return 0 # , nil
93
93
  else
@@ -807,4 +807,12 @@ module Morpheus::Cli::PrintHelper
807
807
  end
808
808
  end
809
809
 
810
+ def sleep_with_dots(sleep_seconds, dots=3, dot_chr=".")
811
+ dot_interval = (sleep_seconds.to_f / dots.to_i)
812
+ dots.to_i.times do |dot_index|
813
+ sleep dot_interval
814
+ print dot_chr
815
+ end
816
+ end
817
+
810
818
  end
@@ -0,0 +1,134 @@
1
+ require 'table_print'
2
+ require 'morpheus/cli/mixins/print_helper'
3
+
4
+ # Mixin for Morpheus::Cli command classes
5
+ # Provides common methods for fetching and printing accounts, roles, and users.
6
+ # The including class must establish @accounts_interface, @roles_interface, @users_interface
7
+ module Morpheus::Cli::ProcessesHelper
8
+
9
+ def self.included(klass)
10
+ klass.send :include, Morpheus::Cli::PrintHelper
11
+ end
12
+
13
+
14
+ def print_process_details(process)
15
+ description_cols = {
16
+ "Process ID" => lambda {|it| it['id'] },
17
+ "Name" => lambda {|it| it['displayName'] },
18
+ "Description" => lambda {|it| it['description'] },
19
+ "Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
20
+ "Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
21
+ "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
22
+ "End Date" => lambda {|it| format_local_dt(it['endDate']) },
23
+ "Duration" => lambda {|it| format_process_duration(it) },
24
+ "Status" => lambda {|it| format_process_status(it) },
25
+ # "# Events" => lambda {|it| (it['events'] || []).size() },
26
+ }
27
+ print_description_list(description_cols, process)
28
+
29
+ if process['error']
30
+ print_h2 "Error"
31
+ print reset
32
+ #puts format_process_error(process_event)
33
+ puts process['error'].to_s.strip
34
+ end
35
+
36
+ if process['output']
37
+ print_h2 "Output"
38
+ print reset
39
+ #puts format_process_error(process_event)
40
+ puts process['output'].to_s.strip
41
+ end
42
+ end
43
+
44
+ def print_process_event_details(process_event)
45
+ # process_event =~ process
46
+ description_cols = {
47
+ "Process ID" => lambda {|it| it['processId'] },
48
+ "Event ID" => lambda {|it| it['id'] },
49
+ "Name" => lambda {|it| it['displayName'] },
50
+ "Description" => lambda {|it| it['description'] },
51
+ "Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
52
+ "Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
53
+ "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
54
+ "End Date" => lambda {|it| format_local_dt(it['endDate']) },
55
+ "Duration" => lambda {|it| format_process_duration(it) },
56
+ "Status" => lambda {|it| format_process_status(it) },
57
+ }
58
+ print_description_list(description_cols, process_event)
59
+
60
+ if process_event['error']
61
+ print_h2 "Error"
62
+ print reset
63
+ #puts format_process_error(process_event)
64
+ puts process_event['error'].to_s.strip
65
+ end
66
+
67
+ if process_event['output']
68
+ print_h2 "Output"
69
+ print reset
70
+ #puts format_process_error(process_event)
71
+ puts process_event['output'].to_s.strip
72
+ end
73
+ end
74
+
75
+
76
+ def format_process_status(process, return_color=cyan)
77
+ out = ""
78
+ status_string = process['status'].to_s
79
+ if status_string == 'complete'
80
+ out << "#{green}#{status_string.upcase}#{return_color}"
81
+ elsif status_string == 'failed'
82
+ out << "#{red}#{status_string.upcase}#{return_color}"
83
+ elsif status_string == 'expired'
84
+ out << "#{red}#{status_string.upcase}#{return_color}"
85
+ else
86
+ out << "#{cyan}#{status_string.upcase}#{return_color}"
87
+ end
88
+ out
89
+ end
90
+
91
+ # decolorize, remove newlines and truncate for table cell
92
+ def format_process_error(process, max_length=50, return_color=cyan)
93
+ out = ""
94
+ if process['error']
95
+ lines = process['error'].split("\n").collect {|line| reset + "#{line.to_s.strip}" }
96
+ out = lines.join(" ")
97
+ if max_length
98
+ out = truncate_string(out, max_length)
99
+ end
100
+ out << return_color if return_color
101
+ end
102
+ out
103
+ end
104
+
105
+ # decolorize, remove newlines and truncate for table cell
106
+ def format_process_output(process, max_length=50, return_color=cyan)
107
+ out = ""
108
+ if process['output']
109
+ lines = process['output'].split("\n").collect {|line| reset + "#{line.to_s.strip}" }
110
+ out = lines.join(" ")
111
+ if max_length
112
+ out = truncate_string(out, max_length)
113
+ end
114
+ out << return_color if return_color
115
+ end
116
+ out
117
+ end
118
+
119
+ # format for either ETA/Duration
120
+ def format_process_duration(process, time_format="%H:%M:%S")
121
+ out = ""
122
+ if process['duration'] && process['duration'] > 0
123
+ out = format_duration_milliseconds(process['duration'], time_format)
124
+ elsif process['statusEta'] && process['statusEta'] > 0
125
+ out = format_duration_milliseconds(process['statusEta'], time_format)
126
+ elsif process['startDate'] && process['endDate']
127
+ out = format_duration(process['startDate'], process['endDate'], time_format)
128
+ else
129
+ ""
130
+ end
131
+ out
132
+ end
133
+
134
+ end
@@ -126,20 +126,10 @@ module Morpheus
126
126
  # I suppose the entered value should take precedence
127
127
  # api_params = api_params.merge(options) # this might be good enough
128
128
  # dup it
129
- select_api_params = {}.merge(api_params || {})
130
- results.each do |k,v|
131
- if v.is_a?(Hash)
132
- # grailsify params k.k2, otherwise you'll get bracked param names like k[k2]
133
- v.each {|k2, v2|
134
- select_api_params["#{k}.#{k2}"] = v2
135
- }
136
- else
137
- # could be String/Symbol duplication issues here.
138
- select_api_params.delete(k.to_sym)
139
- select_api_params[k.to_s] = v
140
- end
141
- end
142
- value = select_prompt(option_type,api_client, select_api_params)
129
+ select_api_params = {}.merge(api_params || {}).merge(results)
130
+ grails_select_api_params = grails_params(select_api_params)
131
+
132
+ value = select_prompt(option_type,api_client, grails_select_api_params)
143
133
  elsif option_type['type'] == 'hidden'
144
134
  value = option_type['defaultValue']
145
135
  input = value
@@ -155,6 +145,21 @@ module Morpheus
155
145
  return results
156
146
  end
157
147
 
148
+ def self.grails_params(data, context=nil)
149
+ params = {}
150
+ data.each do |k,v|
151
+ if v.is_a?(Hash)
152
+ params.merge!(grails_params(v, context ? "#{context}.#{k.to_s}" : k))
153
+ else
154
+ if context
155
+ params["#{context}.#{k.to_s}"] = v
156
+ else
157
+ params[k.to_s] = v
158
+ end
159
+ end
160
+ end
161
+ return params
162
+ end
158
163
 
159
164
  def self.radio_prompt(option_type)
160
165
  value_found = false
@@ -368,7 +373,7 @@ module Morpheus
368
373
  # value = input.empty? ? option_type['defaultValue'] : input
369
374
  if input == '?' && value.nil?
370
375
  help_prompt(option_type)
371
- elsif (!value.nil? || option_type['required'] != true) && input.chomp == 'EOF'
376
+ elsif input.chomp == 'EOF'
372
377
  value_found = true
373
378
  else
374
379
  if value.nil?
@@ -377,7 +382,7 @@ module Morpheus
377
382
  value << input + "\n"
378
383
  end
379
384
  end
380
- return value
385
+ return value ? value.strip : value
381
386
  end
382
387
 
383
388
  def self.password_prompt(option_type)
@@ -1,14 +1,15 @@
1
1
  require 'fileutils'
2
2
  require 'morpheus/cli/cli_command'
3
3
  require 'morpheus/cli/mixins/library_helper'
4
+ require 'morpheus/morpkg'
4
5
 
5
6
  class Morpheus::Cli::LibraryPackagesCommand
6
7
  include Morpheus::Cli::CliCommand
7
8
  include Morpheus::Cli::LibraryHelper
8
9
 
9
10
  set_command_name :'packages'
10
- # register_subcommands :list, :get, :install, :update, :remove, :export
11
- register_subcommands :list, :search, :export
11
+ register_subcommands :list, :search, :get, :install, :update, :remove, :export
12
+ register_subcommands :'install-file' => :install_file
12
13
 
13
14
  # hide until this is released
14
15
  set_command_hidden
@@ -41,7 +42,7 @@ class Morpheus::Cli::LibraryPackagesCommand
41
42
 
42
43
  if options[:dry_run]
43
44
  print_dry_run @packages_interface.dry.list(params)
44
- return
45
+ return 0
45
46
  end
46
47
  json_response = @packages_interface.list(params)
47
48
  if options[:json]
@@ -69,7 +70,7 @@ class Morpheus::Cli::LibraryPackagesCommand
69
70
  description: package['description'],
70
71
  }
71
72
  }
72
- columns = [:code, :name, {:description => {:max_width => 50}}]
73
+ columns = [:code, :name, {:description => {:max_width => 50}}, :version]
73
74
  # custom pretty table columns ...
74
75
  if options[:include_fields]
75
76
  columns = options[:include_fields]
@@ -98,11 +99,14 @@ class Morpheus::Cli::LibraryPackagesCommand
98
99
  optparse.parse!(args)
99
100
  connect(options)
100
101
  begin
102
+ if args[0]
103
+ options[:phrase] = args[0]
104
+ # params['phrase'] = args[0]
105
+ end
101
106
  params.merge!(parse_list_options(options))
102
-
103
107
  if options[:dry_run]
104
108
  print_dry_run @packages_interface.dry.search(params)
105
- return
109
+ return 0
106
110
  end
107
111
  json_response = @packages_interface.search(params)
108
112
  if options[:json]
@@ -126,11 +130,11 @@ class Morpheus::Cli::LibraryPackagesCommand
126
130
  {
127
131
  code: package['code'],
128
132
  name: package['name'],
129
- version: package['version'],
130
- description: package['description']
133
+ description: package['description'],
134
+ versions: (package['versions'] || []).collect {|v| v['version'] }.join(', ')
131
135
  }
132
136
  }
133
- columns = [:code, :name, {:description => {:max_width => 50}}]
137
+ columns = [:code, :name, {:description => {:max_width => 50}}, {:versions => {:max_width => 30}}]
134
138
  # custom pretty table columns ...
135
139
  if options[:include_fields]
136
140
  columns = options[:include_fields]
@@ -149,11 +153,279 @@ class Morpheus::Cli::LibraryPackagesCommand
149
153
  end
150
154
 
151
155
  def get(args)
152
- raise "not yet implemented"
156
+ options = {}
157
+ params = {}
158
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
159
+ opts.banner = subcommand_usage("[code]")
160
+ opts.on( nil, '--versions', "Display Available Versions" ) do
161
+ options[:show_versions] = true
162
+ end
163
+ opts.on( nil, '--objects', "Display Installed Package Objects" ) do
164
+ options[:show_objects] = true
165
+ end
166
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
167
+ opts.footer = "Get details about a package.\n" +
168
+ "[code] is required. This is the package code."
169
+ end
170
+ optparse.parse!(args)
171
+ connect(options)
172
+ if args.count != 1
173
+ print_error Morpheus::Terminal.angry_prompt
174
+ puts_error "#{command_name} get expects 1 argument and received #{args.count} #{args.join(' ')}\n#{optparse}"
175
+ return 1
176
+ end
177
+ begin
178
+ params.merge!(parse_list_options(options))
179
+ params['code'] = args[0]
180
+ if options[:dry_run]
181
+ print_dry_run @packages_interface.dry.info(params)
182
+ return 0
183
+ end
184
+ json_response = @packages_interface.info(params)
185
+ if options[:json]
186
+ puts as_json(json_response, options, "package")
187
+ return 0
188
+ elsif options[:yaml]
189
+ puts as_yaml(json_response, options, "package")
190
+ return 0
191
+ elsif options[:csv]
192
+ puts records_as_csv(json_response["package"], options)
193
+ else
194
+ installed_package = json_response["package"]
195
+ available_package = json_response["availablePackage"]
196
+
197
+ if installed_package.nil? && available_package.nil?
198
+ print yellow,"No package found for code '#{params['code']}'",reset,"\n"
199
+ return 1
200
+ end
201
+
202
+ print_h1 "Package Info"
203
+
204
+ # merge installed and available package info
205
+ package_record = installed_package || available_package
206
+ #package_record['versions'] = available_package['versions'] if available_package
207
+ print cyan
208
+ description_cols = {
209
+ # "Organization" => 'organization',
210
+ "Code" => 'code',
211
+ "Name" => 'name',
212
+ "Description" => 'description',
213
+ # "Type" => lambda {|it| (it['packageType'] || it['type']).to_s },
214
+ "Latest Version" => lambda {|it|
215
+ if available_package && available_package['versions']
216
+ latest_version = available_package['versions'].find {|v| v['latestVersion'] }
217
+ if latest_version.nil?
218
+ sorted_versions = available_package['versions'].sort {|x,y| x['dateCreated'] <=> y['dateCreated'] }
219
+ latest_version = sorted_versions.first()
220
+ end
221
+ latest_version ? latest_version['version'] : ""
222
+ else
223
+ ""
224
+ end
225
+ },
226
+ "Installed Version" => lambda {|it|
227
+ installed_package ? installed_package['version'] : ""
228
+ },
229
+ "Status" => lambda {|it|
230
+ installed_package ? format_package_status(installed_package['status']) : format_package_status(nil)
231
+ },
232
+ }
233
+ if installed_package.nil?
234
+ description_cols.delete("Installed Version")
235
+ end
236
+
237
+ print_description_list(description_cols, package_record)
238
+ # print reset, "\n"
239
+ if options[:show_versions]
240
+ print_h2 "Available Versions"
241
+ if available_package.nil?
242
+ print yellow,"No marketplace package found for code '#{params['code']}'",reset,"\n"
243
+ else
244
+ if available_package['versions'].nil? || available_package['versions'].empty?
245
+ print yellow,"No available versions found",reset,"\n"
246
+ else
247
+ # api is sorting these with latestVersion first, right?
248
+ sorted_versions = available_package['versions'] || []
249
+ #sorted_versions = available_package['versions'].sort {|x,y| x['dateCreated'] <=> y['dateCreated'] }
250
+ rows = sorted_versions.collect {|package_version|
251
+ {
252
+ "PACKAGE VERSION": package_version['version'],
253
+ "MORPHEUS VERSION": package_version['minApplianceVersion'],
254
+ "PUBLISH DATE": format_local_dt(package_version['created'] || package_version['dateCreated'])
255
+ }
256
+ }
257
+ columns = ["PACKAGE VERSION", "MORPHEUS VERSION", "PUBLISH DATE"]
258
+ print cyan
259
+ print as_pretty_table(rows, columns)
260
+ # print reset, "\n"
261
+ end
262
+ end
263
+ end
264
+ if options[:show_objects]
265
+ print_h2 "Package Objects"
266
+ if installed_package.nil?
267
+ print cyan,"No objects to show",reset,"\n"
268
+ else
269
+ if installed_package['objects'].nil? || installed_package['objects'].empty?
270
+ print yellow,"No objects to show",reset,"\n"
271
+ else
272
+ rows = installed_package['objects'].collect {|it|
273
+ {
274
+ type: it['refType'],
275
+ id: it['refId'],
276
+ name: it['displayName']
277
+ }
278
+ }
279
+ columns = [:type, :id, :name]
280
+ print cyan
281
+ print as_pretty_table(rows, columns)
282
+ # print reset, "\n"
283
+ end
284
+ end
285
+ end
286
+
287
+ print reset, "\n"
288
+ return 0
289
+ end
290
+ rescue RestClient::Exception => e
291
+ print_rest_exception(e, options)
292
+ exit 1
293
+ end
153
294
  end
154
295
 
155
296
  def install(args)
156
- raise "not yet implemented"
297
+ full_command_string = "#{command_name} install #{args.join(' ')}".strip
298
+ options = {}
299
+ params = {}
300
+ open_prog = nil
301
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
302
+ opts.banner = subcommand_usage("[code]")
303
+ opts.on('-v','--version VERSION', "Package Version number") do |val|
304
+ params['version'] = val
305
+ end
306
+ # opts.on('--package-version VALUE', String, "Version number for package.") do |val|
307
+ # params['version'] = val
308
+ # end
309
+ opts.on('--organization NAME', String, "Package Organization. Default is morpheus.") do |val|
310
+ params['organization'] = val
311
+ end
312
+ opts.on( '-f', '--force', "Force Install." ) do
313
+ params['force'] = true
314
+ end
315
+ build_common_options(opts, options, [:options, :json, :dry_run, :remote, :quiet])
316
+ opts.footer = "Install a morpheus package from the marketplace.\n" +
317
+ "[code] is required. This is the package code."
318
+ end
319
+ optparse.parse!(args)
320
+ if args.count != 1
321
+ print_error Morpheus::Terminal.angry_prompt
322
+ puts_error "#{command_name} install expects 1 argument and received #{args.count} #{args.join(' ')}\n#{optparse}"
323
+ return 1
324
+ end
325
+ connect(options)
326
+ begin
327
+ params['code'] = args[0]
328
+ if options[:dry_run]
329
+ #print cyan,bold, " - Uploading #{local_file_path} to #{bucket_id}:#{destination} DRY RUN", reset, "\n"
330
+ # print_h1 "DRY RUN"
331
+ print_dry_run @packages_interface.dry.install(params)
332
+ print "\n"
333
+ return 0
334
+ end
335
+
336
+ json_response = @packages_interface.install(params)
337
+ if options[:json]
338
+ puts as_json(json_response, options)
339
+ elsif !options[:quiet]
340
+ package_str = params['code'] || ""
341
+ installed_package = json_response['package']
342
+ if installed_package && installed_package['code']
343
+ package_str = installed_package['code']
344
+ end
345
+ if installed_package && installed_package['version']
346
+ package_str = "#{package_str} #{installed_package['version']}"
347
+ end
348
+ print_green_success "Installed package #{package_str}"
349
+ end
350
+
351
+ rescue RestClient::Exception => e
352
+ print_rest_exception(e, options)
353
+ return 1
354
+ end
355
+ end
356
+
357
+ def install_file(args)
358
+ full_command_string = "#{command_name} install-file #{args.join(' ')}".strip
359
+ options = {}
360
+ params = {}
361
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
362
+ opts.banner = subcommand_usage("[morpkg-file]")
363
+ # opts.on('--url URL', String, "Use a remote URL as the source .morpkg instead of uploading a file.") do |val|
364
+ # params['url'] = val
365
+ # end
366
+ opts.on( '-f', '--force', "Force Install." ) do
367
+ params['force'] = true
368
+ end
369
+ build_common_options(opts, options, [:options, :dry_run, :quiet])
370
+ opts.footer = "Install a morpheus package with a .morpkg file directly.\n" +
371
+ "[morpkg-file] is required. This is the local filepath of a .morpkg to be uploaded and installed."
372
+ end
373
+ optparse.parse!(args)
374
+
375
+ if args.count != 1
376
+ print_error Morpheus::Terminal.angry_prompt
377
+ puts_error "expects 1 argument and received #{args.count} #{args.join(' ')}\n#{optparse}"
378
+ return 1
379
+ end
380
+
381
+ # validate local file path
382
+ local_file_path = File.expand_path(args[0].squeeze('/'))
383
+ if local_file_path == "" || local_file_path == "/" || local_file_path == "."
384
+ print_error Morpheus::Terminal.angry_prompt
385
+ puts_error "bad argument: [morpkg-file]\nFile '#{local_file_path}' is invalid.\n#{optparse}"
386
+ return 1
387
+ end
388
+ if !File.exists?(local_file_path)
389
+ print_error Morpheus::Terminal.angry_prompt
390
+ puts_error "bad argument: [morpkg-file]\nFile '#{local_file_path}' was not found.\n"
391
+ return 1
392
+ end
393
+ if !File.file?(local_file_path)
394
+ print_error Morpheus::Terminal.angry_prompt
395
+ puts_error "bad argument: [morpkg-file]\nFile '#{local_file_path}' is not a file.\n"
396
+ return 1
397
+ end
398
+
399
+ connect(options)
400
+ begin
401
+
402
+ if options[:dry_run]
403
+ #print cyan,bold, " - Uploading #{local_file_path} to #{bucket_id}:#{destination} DRY RUN", reset, "\n"
404
+ # print_h1 "DRY RUN"
405
+ print_dry_run @packages_interface.dry.install_file(local_file_path, params)
406
+ print "\n"
407
+ return 0
408
+ end
409
+
410
+ json_response = @packages_interface.install_file(local_file_path, params)
411
+ if options[:json]
412
+ puts as_json(json_response, options)
413
+ elsif !options[:quiet]
414
+ package_str = params['code'] || ""
415
+ installed_package = json_response['package']
416
+ if installed_package && installed_package['code']
417
+ package_str = installed_package['code']
418
+ end
419
+ if installed_package && installed_package['version']
420
+ package_str = "#{package_str} #{installed_package['version']}"
421
+ end
422
+ print_green_success "Installed package #{package_str}"
423
+ end
424
+
425
+ rescue RestClient::Exception => e
426
+ print_rest_exception(e, options)
427
+ return 1
428
+ end
157
429
  end
158
430
 
159
431
  def update(args)
@@ -164,7 +436,7 @@ class Morpheus::Cli::LibraryPackagesCommand
164
436
  raise "not yet implemented"
165
437
  end
166
438
 
167
- # generate a new .morpkg file
439
+ # download a new .morpkg for remote instanceType(s)
168
440
  def export(args)
169
441
  full_command_string = "#{command_name} export #{args.join(' ')}".strip
170
442
  options = {}
@@ -241,15 +513,15 @@ class Morpheus::Cli::LibraryPackagesCommand
241
513
  end
242
514
  end
243
515
  build_common_options(opts, options, [:options, :dry_run, :quiet])
244
- opts.footer = "Export one or many instance types as a morpheus library package (.morpkg) file.\n" +
516
+ opts.footer = "Export one or many instance types as a morpheus package (.morpkg) file.\n" +
245
517
  "[instance-type] is required. This is the instance type code." +
246
- "--instance-types can bv. This is a list of instance type codes."
518
+ "--instance-types can be export multiple instance types at once. This is a list of instance type codes."
247
519
  end
248
520
  optparse.parse!(args)
249
521
 
250
522
  if args.count != 1 && !instance_type_codes && !params['all']
251
523
  print_error Morpheus::Terminal.angry_prompt
252
- puts_error "#{command_name} export expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
524
+ puts_error "#{command_name} export expects 1 argument and received #{args.count} #{args.join(' ')}\n#{optparse}"
253
525
  return 1
254
526
  end
255
527
  connect(options)
@@ -308,7 +580,7 @@ class Morpheus::Cli::LibraryPackagesCommand
308
580
 
309
581
  if options[:dry_run]
310
582
  print_dry_run @packages_interface.dry.export(params, outfile), full_command_string
311
- return 1
583
+ return 0
312
584
  end
313
585
  if !options[:quiet]
314
586
  print cyan + "Downloading morpheus package file #{outfile} ... "
@@ -362,8 +634,188 @@ class Morpheus::Cli::LibraryPackagesCommand
362
634
  end
363
635
  end
364
636
 
637
+ # generate a new .morpkg for a local source directory
638
+ def build(args)
639
+ full_command_string = "#{command_name} build #{args.join(' ')}".strip
640
+ options = {}
641
+ source_directory = nil
642
+ outfile = nil
643
+ do_overwrite = false
644
+ do_mkdir = false
645
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
646
+ opts.banner = subcommand_usage("[source] [target]")
647
+ opts.on('--source FILE', String, "Source directory of the package being built.") do |val|
648
+ source_directory = val
649
+ end
650
+ opts.on('--target FILE', String, "Destination filename for the .morpkg output file. Default is [code]-[version].morpkg") do |val|
651
+ outfile = val
652
+ end
653
+ opts.on( '-p', '--mkdir', "Create missing directories for [target] if they do not exist." ) do
654
+ do_mkdir = true
655
+ end
656
+ opts.on( '-f', '--force', "Overwrite existing [target] if it exists. Also creates missing directories." ) do
657
+ do_overwrite = true
658
+ do_mkdir = true
659
+ end
660
+
661
+ build_common_options(opts, options, [:options, :dry_run, :quiet])
662
+ opts.footer = "Generate a new morpheus package file. \n" +
663
+ "[source] is required. This is the source directory of the package.\n" +
664
+ "[target] is the output filename. The default is [code]-[version].morpkg"
665
+ end
666
+ optparse.parse!(args)
667
+
668
+ if args.count < 1 || args.count > 2
669
+ print_error Morpheus::Terminal.angry_prompt
670
+ puts_error "wrong number of arguments, expected 1-2 and got #{args.count}\n#{optparse}"
671
+ return 1
672
+ end
673
+ if args[0]
674
+ source_directory = args[0]
675
+ end
676
+ if args[1]
677
+ outfile = args[1]
678
+ end
679
+ if !source_directory
680
+ print_error Morpheus::Terminal.angry_prompt
681
+ puts_error "missing required argument [source]\n#{optparse}"
682
+ return 1
683
+ end
684
+
685
+ # connect(options)
686
+ begin
687
+ # validate source
688
+ source_directory = File.expand_path(source_directory)
689
+ if !File.exists?(source_directory)
690
+ puts_error "#{Morpheus::Terminal.angry_prompt}[source] is invalid. Directory not found: #{source_directory}"
691
+ return 1
692
+ end
693
+ if !File.directory?(source_directory)
694
+ puts_error "#{Morpheus::Terminal.angry_prompt}[source] is invalid. Not a directory: #{source_directory}"
695
+ return 1
696
+ end
697
+ # parse package source
698
+
699
+ package_org = nil
700
+ package_code = nil
701
+ package_version = nil
702
+ package_type = nil
703
+
704
+
705
+ # validate package
706
+ if !package_code.nil? || package_code.empty?
707
+ puts_error "#{Morpheus::Terminal.angry_prompt}package data is invalid. Missing package code."
708
+ return 1
709
+ end
710
+ if package_version.nil? || package_version.empty?
711
+ puts_error "#{Morpheus::Terminal.angry_prompt}package data is invalid. Missing package version."
712
+ return 1
713
+ end
714
+ # determine outfile
715
+ if !outfile
716
+ outfile = File.join(File.dirname(source_directory), "#{package_code}-#{package_version}.morpkg")
717
+ else
718
+ outfile = File.expand_path(outfile)
719
+ end
720
+ if Dir.exists?(outfile)
721
+ puts_error "#{Morpheus::Terminal.angry_prompt}[target] is invalid. It is the name of an existing directory: #{outfile}"
722
+ return 1
723
+ end
724
+ # always a .morpkg
725
+ if outfile[-7..-1] != ".morpkg"
726
+ outfile << ".morpkg"
727
+ end
728
+ destination_dir = File.dirname(outfile)
729
+ if !Dir.exists?(destination_dir)
730
+ if do_mkdir
731
+ print cyan,"Creating local directory #{destination_dir}",reset,"\n"
732
+ FileUtils.mkdir_p(destination_dir)
733
+ else
734
+ puts_error "#{Morpheus::Terminal.angry_prompt}[target] is invalid. Directory not found: #{destination_dir} Use -p to create the missing directory."
735
+ return 1
736
+ end
737
+ end
738
+ if File.exists?(outfile)
739
+ if do_overwrite
740
+ # uhh need to be careful wih the passed filepath here..
741
+ # don't delete, just overwrite.
742
+ # File.delete(outfile)
743
+ else
744
+ puts_error "#{Morpheus::Terminal.angry_prompt}[target] is invalid. File already exists: #{outfile}", "Use -f to overwrite the existing file."
745
+ return 1
746
+ end
747
+ end
748
+
749
+ if options[:dry_run]
750
+ print cyan + "Building morpheus package at #{source_directory} ..."
751
+ print cyan + "DRY RUN" + reset + "\n"
752
+ return 0
753
+ end
754
+ if !options[:quiet]
755
+ print cyan + "Building morpheus package at #{source_directory} ... "
756
+ end
757
+
758
+ # build it
759
+ #http_response, bad_body = @packages_interface.export(params, outfile)
760
+
761
+ # FileUtils.chmod(0600, outfile)
762
+ success = true
763
+ error_msg = nil
764
+ build_outfile = nil
765
+ begin
766
+ build_outfile = Morpheus::Morpkg.build_package(source_directory, outfile, options[:force])
767
+ success = true
768
+ rescue => ex
769
+ error_msg = ex.message
770
+ end
771
+ if success
772
+ if !options[:quiet]
773
+ print green + "SUCCESS" + reset + "\n"
774
+ print green + "Generated #{build_outfile}" + reset + "\n"
775
+ end
776
+ return 0
777
+ else
778
+ if !options[:quiet]
779
+ print red + "ERROR" + reset + "\n"
780
+ if error_msg
781
+ print_error red + error_msg + reset + "\n"
782
+ end
783
+ end
784
+ # F it, just remove a bad result
785
+ # if File.exists?(outfile) && File.file?(outfile)
786
+ # Morpheus::Logging::DarkPrinter.puts "Deleting bad build file: #{outfile}" if Morpheus::Logging.debug?
787
+ # File.delete(outfile)
788
+ # end
789
+ if options[:debug]
790
+ # puts_error error_msg
791
+ end
792
+ return 1
793
+ end
794
+
795
+ rescue RestClient::Exception => e
796
+ print_rest_exception(e, options)
797
+ return 1
798
+ end
799
+ end
800
+
365
801
  private
366
802
 
367
-
803
+ def format_package_status(status_string, return_color=cyan)
804
+ out = ""
805
+ if status_string == 'installed'
806
+ out << "#{green}#{status_string.upcase}#{return_color}"
807
+ elsif status_string == 'uninstalled' || status_string == 'removed'
808
+ out << "#{red}#{status_string.upcase}#{return_color}"
809
+ elsif status_string == 'installing'
810
+ out << "#{cyan}#{status_string.upcase}#{return_color}"
811
+ elsif status_string.nil?
812
+ out << "#{cyan}NOT INSTALLED#{return_color}"
813
+ elsif !status_string.nil?
814
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
815
+ else
816
+ out << ""
817
+ end
818
+ out
819
+ end
368
820
 
369
821
  end