morpheus-cli 2.11.0 → 2.11.1

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.
@@ -14,7 +14,7 @@ module Morpheus
14
14
  @@appliance_credentials_map = nil
15
15
 
16
16
  def initialize(appliance_name, appliance_url)
17
- @appliance_name = appliance_name
17
+ @appliance_name = appliance_name ? appliance_name.to_sym : nil
18
18
  @appliance_url = appliance_url
19
19
  end
20
20
 
@@ -148,22 +148,12 @@ module Morpheus
148
148
  end
149
149
 
150
150
  def load_saved_credentials(reload=false)
151
- if saved_credentials && !reload
152
- return saved_credentials
151
+ if reload || !defined?(@@appliance_credentials_map) || @@appliance_credentials_map.nil?
152
+ @@appliance_credentials_map = load_credentials_file || {}
153
153
  end
154
- #Morpheus::Logging::DarkPrinter.puts "loading credentials for #{appliance_name}" if Morpheus::Logging.debug?
155
- @@appliance_credentials_map = load_credentials_file || {}
156
154
  return @@appliance_credentials_map[@appliance_name]
157
155
  end
158
156
 
159
- # Provides the current credential information, simply :appliance_name => "access_token"
160
- def saved_credentials
161
- if !defined?(@@appliance_credentials_map)
162
- @@appliance_credentials_map = load_credentials_file
163
- end
164
- return @@appliance_credentials_map ? @@appliance_credentials_map[@appliance_name.to_sym] : nil
165
- end
166
-
167
157
  def load_credentials_file
168
158
  fn = credentials_file_path
169
159
  if File.exist? fn
@@ -194,6 +184,7 @@ module Morpheus
194
184
  Morpheus::Logging::DarkPrinter.puts "adding credentials for #{appliance_name} to #{fn}" if Morpheus::Logging.debug?
195
185
  File.open(fn, 'w') {|f| f.write credential_map.to_yaml } #Store
196
186
  FileUtils.chmod(0600, fn)
187
+ @@appliance_credentials_map = credential_map
197
188
  rescue => e
198
189
  puts "failed to save #{fn}. #{e}" if Morpheus::Logging.debug?
199
190
  end
@@ -50,6 +50,11 @@ class Morpheus::Cli::ErrorHandler
50
50
  @stderr.puts "#{red}#{err.message}#{reset}"
51
51
  when RestClient::Exception
52
52
  print_rest_exception(err, options)
53
+ # no stacktrace for now...
54
+ return exit_code
55
+ # if !options[:debug]
56
+ # return exit_code
57
+ # end
53
58
  else
54
59
  @stderr.puts "#{red}Unexpected Error#{reset}"
55
60
  end
@@ -85,6 +90,10 @@ class Morpheus::Cli::ErrorHandler
85
90
 
86
91
  def print_rest_exception(err, options)
87
92
  e = err
93
+ # heh
94
+ if Morpheus::Logging.debug? && options[:debug].nil?
95
+ options[:debug] = true
96
+ end
88
97
  if err.response
89
98
  if options[:debug]
90
99
  begin
@@ -11,7 +11,8 @@ class Morpheus::Cli::Instances
11
11
  include Morpheus::Cli::CliCommand
12
12
  include Morpheus::Cli::ProvisioningHelper
13
13
 
14
- register_subcommands :list, :get, :add, :update, :remove, :logs, :stats, :stop, :start, :restart, :suspend, :eject, :backup, :backups, :stop_service, :start_service, :restart_service, :resize, :clone, :envs, :setenv, :delenv, :security_groups, :apply_security_groups, :firewall_enable, :firewall_disable, :run_workflow, :import_snapshot, :console, :status_check
14
+ register_subcommands :list, :get, :add, :update, :remove, :logs, :stats, :stop, :start, :restart, :actions, :action, :suspend, :eject, :backup, :backups, :stop_service, :start_service, :restart_service, :resize, :clone, :envs, :setenv, :delenv, :security_groups, :apply_security_groups, :firewall_enable, :firewall_disable, :run_workflow, :import_snapshot, :console, :status_check, {:containers => :list_containers}, :scaling, {:'scaling-update' => :scaling_update}
15
+ # register_subcommands {:'lb-update' => :load_balancer_update}
15
16
  alias_subcommand :details, :get
16
17
  set_default_subcommand :list
17
18
 
@@ -36,6 +37,137 @@ class Morpheus::Cli::Instances
36
37
  handle_subcommand(args)
37
38
  end
38
39
 
40
+ def list(args)
41
+ options = {}
42
+ optparse = OptionParser.new do|opts|
43
+ opts.banner = subcommand_usage()
44
+ opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
45
+ options[:group] = val
46
+ end
47
+ opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
48
+ options[:cloud] = val
49
+ end
50
+ build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :dry_run, :remote])
51
+ end
52
+ optparse.parse!(args)
53
+ connect(options)
54
+ begin
55
+ params = {}
56
+ group = options[:group] ? find_group_by_name_or_id_for_provisioning(options[:group]) : nil
57
+ if group
58
+ params['siteId'] = group['id']
59
+ end
60
+
61
+ # argh, this doesn't work because group_id is required for options/clouds
62
+ # cloud = options[:cloud] ? find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud]) : nil
63
+ cloud = options[:cloud] ? find_zone_by_name_or_id(nil, options[:cloud]) : nil
64
+ if cloud
65
+ params['zoneId'] = cloud['id']
66
+ end
67
+
68
+ [:phrase, :offset, :max, :sort, :direction].each do |k|
69
+ params[k] = options[k] unless options[k].nil?
70
+ end
71
+
72
+ if options[:dry_run]
73
+ print_dry_run @instances_interface.dry.list(params)
74
+ return
75
+ end
76
+ json_response = @instances_interface.get(params)
77
+ if options[:json]
78
+ if options[:include_fields]
79
+ json_response = {"instances" => filter_data(json_response["instances"], options[:include_fields]) }
80
+ end
81
+ puts as_json(json_response, options)
82
+ return 0
83
+ elsif options[:yaml]
84
+ if options[:include_fields]
85
+ json_response = {"instances" => filter_data(json_response["instances"], options[:include_fields]) }
86
+ end
87
+ puts as_yaml(json_response, options)
88
+ return 0
89
+ elsif options[:csv]
90
+ # merge stats to be nice here..
91
+ if json_response['instances']
92
+ all_stats = json_response['stats'] || {}
93
+ json_response['instances'].each do |it|
94
+ it['stats'] ||= all_stats[it['id'].to_s] || all_stats[it['id']]
95
+ end
96
+ end
97
+ puts records_as_csv(json_response['instances'], options)
98
+ else
99
+ instances = json_response['instances']
100
+
101
+ title = "Morpheus Instances"
102
+ subtitles = []
103
+ if group
104
+ subtitles << "Group: #{group['name']}".strip
105
+ end
106
+ if cloud
107
+ subtitles << "Cloud: #{cloud['name']}".strip
108
+ end
109
+ if params[:phrase]
110
+ subtitles << "Search: #{params[:phrase]}".strip
111
+ end
112
+ print_h1 title, subtitles
113
+ if instances.empty?
114
+ print yellow,"No instances found.",reset,"\n"
115
+ else
116
+ # print_instances_table(instances)
117
+ # server returns stats in a separate key stats => {"id" => {} }
118
+ # the id is a string right now..for some reason..
119
+ all_stats = json_response['stats'] || {}
120
+ instances.each do |it|
121
+ if !it['stats']
122
+ found_stats = all_stats[it['id'].to_s] || all_stats[it['id']]
123
+ it['stats'] = found_stats # || {}
124
+ end
125
+ end
126
+
127
+ rows = instances.collect {|instance|
128
+ stats = instance['stats']
129
+ cpu_usage_str = !stats ? "" : generate_usage_bar((stats['usedCpu'] || stats['cpuUsage']).to_f, 100, {max_bars: 10})
130
+ memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
131
+ storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
132
+ row = {
133
+ id: instance['id'],
134
+ name: instance['name'],
135
+ connection: format_instance_connection_string(instance),
136
+ environment: instance['instanceContext'],
137
+ nodes: instance['containers'].count,
138
+ status: format_instance_status(instance, cyan),
139
+ type: instance['instanceType']['name'],
140
+ group: !instance['group'].nil? ? instance['group']['name'] : nil,
141
+ cloud: !instance['cloud'].nil? ? instance['cloud']['name'] : nil,
142
+ version: instance['instanceVersion'] ? instance['instanceVersion'] : '',
143
+ cpu: cpu_usage_str + cyan,
144
+ memory: memory_usage_str + cyan,
145
+ storage: storage_usage_str + cyan
146
+ }
147
+ row
148
+ }
149
+ columns = [:id, :name, :group, :cloud, :type, :version, :environment, :nodes, {:connection => {max_width: 30}}, :status]
150
+ term_width = current_terminal_width()
151
+ if term_width > 190
152
+ columns += [:cpu, :memory, :storage]
153
+ end
154
+ # custom pretty table columns ...
155
+ if options[:include_fields]
156
+ columns = options[:include_fields]
157
+ end
158
+ print cyan
159
+ print as_pretty_table(rows, columns, options)
160
+ print reset
161
+ print_results_pagination(json_response)
162
+ end
163
+ print reset,"\n"
164
+ end
165
+ rescue RestClient::Exception => e
166
+ print_rest_exception(e, options)
167
+ exit 1
168
+ end
169
+ end
170
+
39
171
  def add(args)
40
172
  options = {}
41
173
  optparse = OptionParser.new do|opts|
@@ -76,7 +208,7 @@ class Morpheus::Cli::Instances
76
208
 
77
209
  payload = prompt_new_instance(options)
78
210
  payload[:copies] = options[:copies] if options[:copies] && options[:copies] > 0
79
- payload[:layoutSize] = options[:layout_size] if options[:layout_size] && options[:layout_size] > 0
211
+ payload[:layoutSize] = options[:layout_size] if options[:layout_size] && options[:layout_size] > 0 # aka Scale Factor
80
212
  if options[:dry_run]
81
213
  print_dry_run @instances_interface.dry.create(payload)
82
214
  return
@@ -87,7 +219,7 @@ class Morpheus::Cli::Instances
87
219
  else
88
220
  instance_name = json_response["instance"]["name"]
89
221
  print_green_success "Provisioning instance #{instance_name}"
90
- list([])
222
+ #list([])
91
223
  end
92
224
  rescue RestClient::Exception => e
93
225
  print_rest_exception(e, options)
@@ -133,7 +265,7 @@ class Morpheus::Cli::Instances
133
265
  exit 1
134
266
  end
135
267
 
136
- instance_keys = ['name', 'description', 'instanceContext', 'tags']
268
+ instance_keys = ['name', 'description', 'instanceContext', 'tags','configId','configRole','configGroup']
137
269
  params = params.select {|k,v| instance_keys.include?(k) }
138
270
  params['tags'] = params['tags'].split(',').collect {|it| it.to_s.strip }.compact.uniq if params['tags']
139
271
  payload['instance'].merge!(params)
@@ -147,7 +279,7 @@ class Morpheus::Cli::Instances
147
279
  puts as_json(json_response, options)
148
280
  else
149
281
  print_green_success "Updated instance #{instance['name']}"
150
- list([])
282
+ #list([])
151
283
  end
152
284
 
153
285
  rescue RestClient::Exception => e
@@ -357,6 +489,24 @@ class Morpheus::Cli::Instances
357
489
  options = {}
358
490
  optparse = OptionParser.new do|opts|
359
491
  opts.banner = subcommand_usage("[name]")
492
+ opts.on( nil, '--containers', "Display Instance Containers" ) do
493
+ options[:include_containers] = true
494
+ end
495
+ opts.on( nil, '--nodes', "Alias for --containers" ) do
496
+ options[:include_containers] = true
497
+ end
498
+ opts.on( nil, '--vms', "Alias for --containers" ) do
499
+ options[:include_containers] = true
500
+ end
501
+ opts.on( nil, '--scaling', "Display Instance Scaling Settings" ) do
502
+ options[:include_scaling] = true
503
+ end
504
+ # opts.on( nil, '--threshold', "Alias for --scaling" ) do
505
+ # options[:include_scaling] = true
506
+ # end
507
+ opts.on( nil, '--lb', "Display Load Balancer Details" ) do
508
+ options[:include_lb] = true
509
+ end
360
510
  build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
361
511
  end
362
512
  optparse.parse!(args)
@@ -403,7 +553,32 @@ class Morpheus::Cli::Instances
403
553
  end
404
554
  instance = json_response['instance']
405
555
  stats = json_response['stats'] || {}
406
- # load_balancers = stats = json_response['loadBalancers'] || {}
556
+ # load_balancers = json_response['loadBalancers'] || {}
557
+
558
+ # containers are fetched via separate api call
559
+ containers = nil
560
+ if options[:include_containers]
561
+ containers = @instances_interface.containers(instance['id'])['containers']
562
+ end
563
+
564
+ # threshold is fetched via separate api call too
565
+ instance_threshold = nil
566
+ if options[:include_scaling]
567
+ instance_threshold = @instances_interface.threshold(instance['id'])['instanceThreshold']
568
+ end
569
+
570
+ # loadBalancers is returned via show
571
+ # parse the current api format of loadBalancers.first.lbs.first
572
+ current_instance_lb = nil
573
+ current_load_balancer_port = nil
574
+ # if options[:include_lb]
575
+ # #load_balancers = @instances_interface.load_balancers(instance['id'])['loadBalancers']
576
+ # end
577
+ if json_response['loadBalancers'] && json_response['loadBalancers'][0] && json_response['loadBalancers'][0]['lbs'] && json_response['loadBalancers'][0]['lbs'][0]
578
+ current_instance_lb = json_response['loadBalancers'][0]['lbs'][0]
579
+ #current_load_balancer = current_instance_lb['loadBalancer']
580
+ #current_load_balancer_port = current_instance_lb['port']
581
+ end
407
582
 
408
583
  print_h1 "Instance Details"
409
584
  print cyan
@@ -423,13 +598,179 @@ class Morpheus::Cli::Instances
423
598
  }
424
599
  print_description_list(description_cols, instance)
425
600
 
426
- if (stats)
601
+ if stats
427
602
  print_h2 "Instance Usage"
428
603
  print_stats_usage(stats)
429
604
  end
430
605
  print reset, "\n"
431
606
 
432
- #puts instance
607
+ if options[:include_containers]
608
+ print_h2 "Instance Containers"
609
+
610
+ if containers.empty?
611
+ print yellow,"No containers found for instance.",reset,"\n"
612
+ else
613
+
614
+ rows = containers.collect {|container|
615
+ stats = container['stats']
616
+ cpu_usage_str = !stats ? "" : generate_usage_bar((stats['usedCpu'] || stats['cpuUsage']).to_f, 100, {max_bars: 10})
617
+ memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
618
+ storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
619
+ row = {
620
+ id: container['id'],
621
+ status: format_container_status(container),
622
+ name: container['server'] ? container['server']['name'] : '(no server)', # there is a server.displayName too?
623
+ type: container['containerType'] ? container['containerType']['name'] : '',
624
+ cloud: container['cloud'] ? container['cloud']['name'] : '',
625
+ location: format_container_connection_string(container),
626
+ cpu: cpu_usage_str + cyan,
627
+ memory: memory_usage_str + cyan,
628
+ storage: storage_usage_str + cyan
629
+ }
630
+ row
631
+ }
632
+ columns = [:id, :status, :name, :type, :cloud, :location]
633
+ term_width = current_terminal_width()
634
+ if term_width > 190
635
+ columns += [:cpu, :memory, :storage]
636
+ end
637
+ # custom pretty table columns ...
638
+ if options[:include_fields]
639
+ columns = options[:include_fields]
640
+ end
641
+ print cyan
642
+ print as_pretty_table(rows, columns, options)
643
+ print reset
644
+ #print_results_pagination({size: containers.size, total: containers.size}) # mock pagination
645
+ end
646
+ print reset,"\n"
647
+ end
648
+
649
+ # if options[:include_lb]
650
+ if current_instance_lb
651
+ print_h2 "Load Balancer"
652
+ print cyan
653
+ description_cols = {
654
+ "LB ID" => lambda {|it| it['loadBalancer']['id'] },
655
+ "Name" => lambda {|it| it['loadBalancer']['name'] },
656
+ "Type" => lambda {|it| it['loadBalancer']['type'] ? it['loadBalancer']['type']['name'] : '' },
657
+ "Host Name" => lambda {|it| it['loadBalancer']['host'] }, # instance.hostName ?
658
+ "Port" => lambda {|it| it['port']['port'] },
659
+ "Protocol" => lambda {|it| it['port']['proxyProtocol'] },
660
+ "SSL Enabled" => lambda {|it| format_boolean it['port']['sslEnabled'] },
661
+ "Cert" => lambda {|it| it['port']['sslCert'] ? it['port']['sslCert']['name'] : 'N/A' } # api needs to return this too..
662
+ }
663
+ print_description_list(description_cols, current_instance_lb)
664
+ print "\n", reset
665
+ end
666
+ # end
667
+
668
+ if options[:include_scaling]
669
+ print_h2 "Instance Scaling"
670
+ if instance_threshold.nil? || instance_threshold.empty?
671
+ print yellow,"No scaling settings applied to this instance.",reset,"\n"
672
+ else
673
+ print cyan
674
+ print_instance_threshold_description_list(instance_threshold)
675
+ print reset,"\n"
676
+ end
677
+ end
678
+ return 0
679
+ rescue RestClient::Exception => e
680
+ print_rest_exception(e, options)
681
+ exit 1
682
+ end
683
+ end
684
+
685
+ def list_containers(args)
686
+ options = {}
687
+ optparse = OptionParser.new do|opts|
688
+ opts.banner = subcommand_usage("[name]")
689
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
690
+ end
691
+ optparse.parse!(args)
692
+ if args.count < 1
693
+ puts optparse
694
+ exit 1
695
+ end
696
+ connect(options)
697
+ id_list = parse_id_list(args)
698
+ return run_command_for_each_arg(id_list) do |arg|
699
+ _list_containers(arg, options)
700
+ end
701
+ end
702
+
703
+ def _list_containers(arg, options)
704
+ begin
705
+ instance = find_instance_by_name_or_id(arg)
706
+ return 1 if instance.nil?
707
+ if options[:dry_run]
708
+ print_dry_run @instances_interface.dry.containers(instance['id'], params)
709
+ return
710
+ end
711
+ json_response = @instances_interface.containers(instance['id'])
712
+ if options[:json]
713
+ if options[:include_fields]
714
+ json_response = {"containers" => filter_data(json_response["containers"], options[:include_fields]) }
715
+ end
716
+ puts as_json(json_response, options)
717
+ return 0
718
+ elsif options[:yaml]
719
+ if options[:include_fields]
720
+ json_response = {"containers" => filter_data(json_response["containers"], options[:include_fields]) }
721
+ end
722
+ puts as_yaml(json_response, options)
723
+ return 0
724
+ end
725
+
726
+ if options[:csv]
727
+ puts records_as_csv(json_response['containers'], options)
728
+ return 0
729
+ end
730
+
731
+
732
+ containers = json_response['containers']
733
+
734
+ title = "Instance Containers: #{instance['name']} (#{instance['instanceType']['name']})"
735
+ print_h1 title
736
+ if containers.empty?
737
+ print yellow,"No containers found for instance.",reset,"\n"
738
+ else
739
+
740
+ rows = containers.collect {|container|
741
+ stats = container['stats']
742
+ cpu_usage_str = !stats ? "" : generate_usage_bar((stats['usedCpu'] || stats['cpuUsage']).to_f, 100, {max_bars: 10})
743
+ memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
744
+ storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
745
+ row = {
746
+ id: container['id'],
747
+ status: format_container_status(container),
748
+ name: container['server'] ? container['server']['name'] : '(no server)', # there is a server.displayName too?
749
+ type: container['containerType'] ? container['containerType']['name'] : '',
750
+ cloud: container['cloud'] ? container['cloud']['name'] : '',
751
+ location: format_container_connection_string(container),
752
+ cpu: cpu_usage_str + cyan,
753
+ memory: memory_usage_str + cyan,
754
+ storage: storage_usage_str + cyan
755
+ }
756
+ row
757
+ }
758
+ columns = [:id, :status, :name, :type, :cloud, :location]
759
+ term_width = current_terminal_width()
760
+ if term_width > 190
761
+ columns += [:cpu, :memory, :storage]
762
+ end
763
+ # custom pretty table columns ...
764
+ if options[:include_fields]
765
+ columns = options[:include_fields]
766
+ end
767
+ print cyan
768
+ print as_pretty_table(rows, columns, options)
769
+ print reset
770
+ print_results_pagination({size: containers.size, total: containers.size}) # mock pagination
771
+ end
772
+ print reset,"\n"
773
+
433
774
  rescue RestClient::Exception => e
434
775
  print_rest_exception(e, options)
435
776
  exit 1
@@ -461,13 +802,14 @@ class Morpheus::Cli::Instances
461
802
  return
462
803
  end
463
804
  backups = json_response['backups']
464
- stats = json_response['stats'] || {}
465
- # load_balancers = stats = json_response['loadBalancers'] || {}
466
805
 
467
806
  print_h1 "Instance Backups: #{instance['name']} (#{instance['instanceType']['name']})"
468
- backup_rows = backups.collect {|it| {id: it['id'], name: it['name'], dateCreated: it['dateCreated']} }
807
+ backup_rows = backups.collect {|r|
808
+ it = r['backup']
809
+ {id: it['id'], name: it['name'], dateCreated: format_local_dt(it['dateCreated'])}
810
+ }
469
811
  print cyan
470
- tp backup_rows, [
812
+ puts as_pretty_table backup_rows, [
471
813
  :id,
472
814
  :name,
473
815
  {:dateCreated => {:display_name => "Date Created"} }
@@ -656,7 +998,7 @@ class Morpheus::Cli::Instances
656
998
  options = {}
657
999
  optparse = OptionParser.new do|opts|
658
1000
  opts.banner = subcommand_usage("[name]")
659
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
1001
+ build_common_options(opts, options, [:auto_confirm, :quiet, :json, :dry_run, :remote])
660
1002
  end
661
1003
  optparse.parse!(args)
662
1004
  if args.count < 1
@@ -676,9 +1018,10 @@ class Morpheus::Cli::Instances
676
1018
  json_response = @instances_interface.stop(instance['id'])
677
1019
  if options[:json]
678
1020
  puts as_json(json_response, options)
679
- print "\n"
1021
+ elsif !options[:quiet]
1022
+ print green, "Stopping instance #{instance['name']}", reset, "\n"
680
1023
  end
681
- return
1024
+ return 0
682
1025
  rescue RestClient::Exception => e
683
1026
  print_rest_exception(e, options)
684
1027
  exit 1
@@ -701,14 +1044,16 @@ class Morpheus::Cli::Instances
701
1044
  instance = find_instance_by_name_or_id(args[0])
702
1045
  if options[:dry_run]
703
1046
  print_dry_run @instances_interface.dry.start(instance['id'])
704
- return
1047
+ return 0
705
1048
  end
706
1049
  json_response = @instances_interface.start(instance['id'])
707
1050
  if options[:json]
708
1051
  puts as_json(json_response, options)
709
1052
  return 0
1053
+ elsif !options[:quiet]
1054
+ print green, "Starting instance #{instance['name']}", reset, "\n"
710
1055
  end
711
- return
1056
+ return 0
712
1057
  rescue RestClient::Exception => e
713
1058
  print_rest_exception(e, options)
714
1059
  exit 1
@@ -719,7 +1064,7 @@ class Morpheus::Cli::Instances
719
1064
  options = {}
720
1065
  optparse = OptionParser.new do|opts|
721
1066
  opts.banner = subcommand_usage("[name]")
722
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
1067
+ build_common_options(opts, options, [:auto_confirm, :quiet, :json, :dry_run, :remote])
723
1068
  end
724
1069
  optparse.parse!(args)
725
1070
  if args.count < 1
@@ -734,13 +1079,15 @@ class Morpheus::Cli::Instances
734
1079
  end
735
1080
  if options[:dry_run]
736
1081
  print_dry_run @instances_interface.dry.restart(instance['id'])
737
- return
1082
+ return 0
738
1083
  end
739
1084
  json_response = @instances_interface.restart(instance['id'])
740
1085
  if options[:json]
741
1086
  puts as_json(json_response, options)
1087
+ elsif !options[:quiet]
1088
+ print green, "Stopping instance #{instance['name']}", reset, "\n"
742
1089
  end
743
- return
1090
+ return 0
744
1091
  rescue RestClient::Exception => e
745
1092
  print_rest_exception(e, options)
746
1093
  exit 1
@@ -751,7 +1098,7 @@ class Morpheus::Cli::Instances
751
1098
  options = {}
752
1099
  optparse = OptionParser.new do|opts|
753
1100
  opts.banner = subcommand_usage("[name]")
754
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
1101
+ build_common_options(opts, options, [:auto_confirm, :quiet, :json, :dry_run, :remote])
755
1102
  end
756
1103
  optparse.parse!(args)
757
1104
  if args.count < 1
@@ -782,8 +1129,8 @@ class Morpheus::Cli::Instances
782
1129
  def eject(args)
783
1130
  options = {}
784
1131
  optparse = OptionParser.new do|opts|
785
- opts.banner = subcommand_usage("restart [name]")
786
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
1132
+ opts.banner = subcommand_usage("[name]")
1133
+ build_common_options(opts, options, [:auto_confirm, :quiet, :json, :dry_run, :remote])
787
1134
  end
788
1135
  optparse.parse!(args)
789
1136
  if args.count < 1
@@ -793,9 +1140,9 @@ class Morpheus::Cli::Instances
793
1140
  connect(options)
794
1141
  begin
795
1142
  instance = find_instance_by_name_or_id(args[0])
796
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to eject this instance?", options)
797
- exit 1
798
- end
1143
+ # unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to eject this instance?", options)
1144
+ # exit 1
1145
+ # end
799
1146
  if options[:dry_run]
800
1147
  print_dry_run @instances_interface.dry.eject(instance['id'])
801
1148
  return
@@ -815,7 +1162,7 @@ class Morpheus::Cli::Instances
815
1162
  options = {}
816
1163
  optparse = OptionParser.new do|opts|
817
1164
  opts.banner = subcommand_usage("[name]")
818
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
1165
+ build_common_options(opts, options, [:auto_confirm, :quiet, :json, :dry_run, :remote])
819
1166
  end
820
1167
  optparse.parse!(args)
821
1168
  if args.count < 1
@@ -825,18 +1172,18 @@ class Morpheus::Cli::Instances
825
1172
  connect(options)
826
1173
  begin
827
1174
  instance = find_instance_by_name_or_id(args[0])
828
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to stop this instance?", options)
1175
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to stop service on this instance?", options)
829
1176
  exit 1
830
1177
  end
831
1178
  if options[:dry_run]
832
1179
  print_dry_run @instances_interface.dry.stop(instance['id'],false)
833
- return
1180
+ return 0
834
1181
  end
835
1182
  json_response = @instances_interface.stop(instance['id'],false)
836
1183
  if options[:json]
837
1184
  puts as_json(json_response, options)
838
- else
839
- puts "Stopping service on #{args[0]}"
1185
+ elsif !options[:quiet]
1186
+ print green, "Stopping service on instance #{instance['name']}", reset, "\n"
840
1187
  end
841
1188
  return 0
842
1189
  rescue RestClient::Exception => e
@@ -849,7 +1196,7 @@ class Morpheus::Cli::Instances
849
1196
  options = {}
850
1197
  optparse = OptionParser.new do|opts|
851
1198
  opts.banner = subcommand_usage("[name]")
852
- build_common_options(opts, options, [:json, :dry_run, :remote])
1199
+ build_common_options(opts, options, [:quiet, :json, :dry_run, :remote])
853
1200
  end
854
1201
  optparse.parse!(args)
855
1202
  if args.count < 1
@@ -861,15 +1208,14 @@ class Morpheus::Cli::Instances
861
1208
  instance = find_instance_by_name_or_id(args[0])
862
1209
  if options[:dry_run]
863
1210
  print_dry_run @instances_interface.dry.start(instance['id'], false)
864
- return
1211
+ return 0
865
1212
  end
866
1213
  json_response = @instances_interface.start(instance['id'],false)
867
1214
  if options[:json]
868
1215
  puts as_json(json_response, options)
869
- else
870
- puts "Starting service on #{args[0]}"
1216
+ elsif !options[:quiet]
1217
+ print green, "Starting service on instance #{instance['name']}", reset, "\n"
871
1218
  end
872
- return 0
873
1219
  rescue RestClient::Exception => e
874
1220
  print_rest_exception(e, options)
875
1221
  exit 1
@@ -880,7 +1226,7 @@ class Morpheus::Cli::Instances
880
1226
  options = {}
881
1227
  optparse = OptionParser.new do|opts|
882
1228
  opts.banner = subcommand_usage("[name]")
883
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
1229
+ build_common_options(opts, options, [:auto_confirm, :quiet, :json, :dry_run, :remote])
884
1230
  end
885
1231
  optparse.parse!(args)
886
1232
  if args.count < 1
@@ -890,26 +1236,168 @@ class Morpheus::Cli::Instances
890
1236
  connect(options)
891
1237
  begin
892
1238
  instance = find_instance_by_name_or_id(args[0])
893
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to restart this instance?", options)
1239
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to restart service on this instance?", options)
894
1240
  exit 1
895
1241
  end
896
1242
  if options[:dry_run]
897
1243
  print_dry_run @instances_interface.dry.restart(instance['id'],false)
898
- return
1244
+ return 0
899
1245
  end
900
1246
  json_response = @instances_interface.restart(instance['id'],false)
901
1247
  if options[:json]
902
1248
  puts as_json(json_response, options)
1249
+ elsif !options[:quiet]
1250
+ print green, "Restarting service on instance #{instance['name']}", reset, "\n"
1251
+ end
1252
+ return 0
1253
+ rescue RestClient::Exception => e
1254
+ print_rest_exception(e, options)
1255
+ exit 1
1256
+ end
1257
+ end
1258
+
1259
+ def actions(args)
1260
+ options = {}
1261
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
1262
+ opts.banner = subcommand_usage("[id or name list]")
1263
+ opts.footer = "This outputs the list of the actions available to specified instance(s)."
1264
+ build_common_options(opts, options, [:json, :dry_run, :remote])
1265
+ end
1266
+ optparse.parse!(args)
1267
+ if args.count < 1
1268
+ print_error Morpheus::Terminal.angry_prompt
1269
+ puts_error "#{command_name} actions requires argument [id or name list]\n#{optparse}"
1270
+ return 1
1271
+ end
1272
+ connect(options)
1273
+ id_list = parse_id_list(args)
1274
+ instances = []
1275
+ id_list.each do |instance_id|
1276
+ instance = find_instance_by_name_or_id(instance_id)
1277
+ if instance.nil?
1278
+ # return 1
903
1279
  else
904
- puts "Restarting service on instance #{args[0]}"
1280
+ instances << instance
905
1281
  end
906
- return
1282
+ end
1283
+ if instances.size != id_list.size
1284
+ #puts_error "instances not found"
1285
+ return 1
1286
+ end
1287
+ instance_ids = instances.collect {|instance| instance["id"] }
1288
+ begin
1289
+ # instance = find_instance_by_name_or_id(args[0])
1290
+ if options[:dry_run]
1291
+ print_dry_run @instances_interface.dry.available_actions(instance_ids)
1292
+ return 0
1293
+ end
1294
+ json_response = @instances_interface.available_actions(instance_ids)
1295
+ if options[:json]
1296
+ puts as_json(json_response, options)
1297
+ else
1298
+ title = "Instance Actions: #{anded_list(id_list)}"
1299
+ print_h1 title
1300
+ available_actions = json_response["actions"]
1301
+ if (available_actions && available_actions.size > 0)
1302
+ print as_pretty_table(available_actions, [:name, :code])
1303
+ print reset, "\n"
1304
+ else
1305
+ print "#{yellow}No available actions#{reset}\n\n"
1306
+ end
1307
+ end
1308
+ return 0
907
1309
  rescue RestClient::Exception => e
908
1310
  print_rest_exception(e, options)
909
1311
  exit 1
910
1312
  end
911
1313
  end
912
1314
 
1315
+ def action(args)
1316
+ options = {}
1317
+ action_id = nil
1318
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
1319
+ opts.banner = subcommand_usage("[id list] -a CODE")
1320
+ opts.on('-a', '--action CODE', "Instance Action CODE to execute") do |val|
1321
+ action_id = val.to_s
1322
+ end
1323
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
1324
+ opts.footer = "Execute an action for a instance or instances"
1325
+ end
1326
+ optparse.parse!(args)
1327
+ if args.count < 1
1328
+ print_error Morpheus::Terminal.angry_prompt
1329
+ puts_error "[id] argument is required"
1330
+ puts_error optparse
1331
+ return 1
1332
+ end
1333
+ connect(options)
1334
+ id_list = parse_id_list(args)
1335
+ instances = []
1336
+ id_list.each do |instance_id|
1337
+ instance = find_instance_by_name_or_id(instance_id)
1338
+ if instance.nil?
1339
+ # return 1
1340
+ else
1341
+ instances << instance
1342
+ end
1343
+ end
1344
+ if instances.size != id_list.size
1345
+ #puts_error "instances not found"
1346
+ return 1
1347
+ end
1348
+ instance_ids = instances.collect {|instance| instance["id"] }
1349
+
1350
+ # figure out what action to run
1351
+ available_actions = @instances_interface.available_actions(instance_ids)["actions"]
1352
+ if available_actions.empty?
1353
+ if instance_ids.size > 1
1354
+ print_red_alert "The specified instances have no available actions in common."
1355
+ else
1356
+ print_red_alert "The specified instance has no available actions."
1357
+ end
1358
+ return 1
1359
+ end
1360
+ instance_action = nil
1361
+ if action_id.nil?
1362
+ available_actions_dropdown = available_actions.collect {|act| {'name' => act["name"], 'value' => act["code"]} } # already sorted
1363
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'code', 'type' => 'select', 'fieldLabel' => 'Instance Action', 'selectOptions' => available_actions_dropdown, 'required' => true, 'description' => 'Choose the instance action to execute'}], options[:options])
1364
+ action_id = v_prompt['code']
1365
+ instance_action = available_actions.find {|act| act['code'].to_s == action_id.to_s }
1366
+ else
1367
+ instance_action = available_actions.find {|act| act['code'].to_s == action_id.to_s || act['name'].to_s.downcase == action_id.to_s.downcase }
1368
+ action_id = instance_action["code"] if instance_action
1369
+ end
1370
+ if !instance_action
1371
+ # for testing bogus actions..
1372
+ # instance_action = {"id" => action_id, "name" => "Unknown"}
1373
+ raise_command_error "Instance Action '#{action_id}' not found."
1374
+ end
1375
+
1376
+ action_display_name = "#{instance_action['name']} [#{instance_action['code']}]"
1377
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to perform action #{action_display_name} on #{id_list.size == 1 ? 'instance' : 'instances'} #{anded_list(id_list)}?", options)
1378
+ return 9, "aborted command"
1379
+ end
1380
+
1381
+ # return run_command_for_each_arg(containers) do |arg|
1382
+ # _action(arg, action_id, options)
1383
+ # end
1384
+ if options[:dry_run]
1385
+ print_dry_run @instances_interface.dry.action(instance_ids, action_id)
1386
+ return 0
1387
+ end
1388
+ json_response = @instances_interface.action(instance_ids, action_id)
1389
+ # just assume json_response["success"] == true, it always is with 200 OK
1390
+ if options[:json]
1391
+ puts as_json(json_response, options)
1392
+ elsif !options[:quiet]
1393
+ # containers.each do |container|
1394
+ # print green, "Action #{action_display_name} performed on container #{container['id']}", reset, "\n"
1395
+ # end
1396
+ print green, "Action #{action_display_name} performed on #{id_list.size == 1 ? 'instance' : 'instances'} #{anded_list(id_list)}", reset, "\n"
1397
+ end
1398
+ return 0
1399
+ end
1400
+
913
1401
  def resize(args)
914
1402
  options = {}
915
1403
  optparse = OptionParser.new do|opts|
@@ -978,7 +1466,7 @@ class Morpheus::Cli::Instances
978
1466
  return 0
979
1467
  else
980
1468
  print_green_success "Resizing instance #{instance['name']}"
981
- list([])
1469
+ #list([])
982
1470
  end
983
1471
  rescue RestClient::Exception => e
984
1472
  print_rest_exception(e, options)
@@ -1021,137 +1509,6 @@ class Morpheus::Cli::Instances
1021
1509
  end
1022
1510
  end
1023
1511
 
1024
- def list(args)
1025
- options = {}
1026
- optparse = OptionParser.new do|opts|
1027
- opts.banner = subcommand_usage()
1028
- opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
1029
- options[:group] = val
1030
- end
1031
- opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
1032
- options[:cloud] = val
1033
- end
1034
- build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :dry_run, :remote])
1035
- end
1036
- optparse.parse!(args)
1037
- connect(options)
1038
- begin
1039
- params = {}
1040
- group = options[:group] ? find_group_by_name_or_id_for_provisioning(options[:group]) : nil
1041
- if group
1042
- params['siteId'] = group['id']
1043
- end
1044
-
1045
- # argh, this doesn't work because group_id is required for options/clouds
1046
- # cloud = options[:cloud] ? find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud]) : nil
1047
- cloud = options[:cloud] ? find_zone_by_name_or_id(nil, options[:cloud]) : nil
1048
- if cloud
1049
- params['zoneId'] = cloud['id']
1050
- end
1051
-
1052
- [:phrase, :offset, :max, :sort, :direction].each do |k|
1053
- params[k] = options[k] unless options[k].nil?
1054
- end
1055
-
1056
- if options[:dry_run]
1057
- print_dry_run @instances_interface.dry.list(params)
1058
- return
1059
- end
1060
- json_response = @instances_interface.get(params)
1061
- if options[:json]
1062
- if options[:include_fields]
1063
- json_response = {"instances" => filter_data(json_response["instances"], options[:include_fields]) }
1064
- end
1065
- puts as_json(json_response, options)
1066
- return 0
1067
- elsif options[:yaml]
1068
- if options[:include_fields]
1069
- json_response = {"instances" => filter_data(json_response["instances"], options[:include_fields]) }
1070
- end
1071
- puts as_yaml(json_response, options)
1072
- return 0
1073
- elsif options[:csv]
1074
- # merge stats to be nice here..
1075
- if json_response['instances']
1076
- all_stats = json_response['stats'] || {}
1077
- json_response['instances'].each do |it|
1078
- it['stats'] ||= all_stats[it['id'].to_s] || all_stats[it['id']]
1079
- end
1080
- end
1081
- puts records_as_csv(json_response['instances'], options)
1082
- else
1083
- instances = json_response['instances']
1084
-
1085
- title = "Morpheus Instances"
1086
- subtitles = []
1087
- if group
1088
- subtitles << "Group: #{group['name']}".strip
1089
- end
1090
- if cloud
1091
- subtitles << "Cloud: #{cloud['name']}".strip
1092
- end
1093
- if params[:phrase]
1094
- subtitles << "Search: #{params[:phrase]}".strip
1095
- end
1096
- print_h1 title, subtitles
1097
- if instances.empty?
1098
- print yellow,"No instances found.",reset,"\n"
1099
- else
1100
- # print_instances_table(instances)
1101
- # server returns stats in a separate key stats => {"id" => {} }
1102
- # the id is a string right now..for some reason..
1103
- all_stats = json_response['stats'] || {}
1104
- instances.each do |it|
1105
- if !it['stats']
1106
- found_stats = all_stats[it['id'].to_s] || all_stats[it['id']]
1107
- it['stats'] = found_stats # || {}
1108
- end
1109
- end
1110
-
1111
- rows = instances.collect {|instance|
1112
- stats = instance['stats']
1113
- cpu_usage_str = !stats ? "" : generate_usage_bar((stats['usedCpu'] || stats['cpuUsage']).to_f, 100, {max_bars: 10})
1114
- memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
1115
- storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
1116
- row = {
1117
- id: instance['id'],
1118
- name: instance['name'],
1119
- connection: format_instance_connection_string(instance),
1120
- environment: instance['instanceContext'],
1121
- nodes: instance['containers'].count,
1122
- status: format_instance_status(instance, cyan),
1123
- type: instance['instanceType']['name'],
1124
- group: !instance['group'].nil? ? instance['group']['name'] : nil,
1125
- cloud: !instance['cloud'].nil? ? instance['cloud']['name'] : nil,
1126
- version: instance['instanceVersion'] ? instance['instanceVersion'] : '',
1127
- cpu: cpu_usage_str + cyan,
1128
- memory: memory_usage_str + cyan,
1129
- storage: storage_usage_str + cyan
1130
- }
1131
- row
1132
- }
1133
- columns = [:id, :name, :group, :cloud, :type, :version, :environment, :nodes, {:connection => {max_width: 20}}, :status]
1134
- term_width = current_terminal_width()
1135
- if term_width > 190
1136
- columns += [:cpu, :memory, :storage]
1137
- end
1138
- # custom pretty table columns ...
1139
- if options[:include_fields]
1140
- columns = options[:include_fields]
1141
- end
1142
- print cyan
1143
- print as_pretty_table(rows, columns, options)
1144
- print reset
1145
- print_results_pagination(json_response)
1146
- end
1147
- print reset,"\n"
1148
- end
1149
- rescue RestClient::Exception => e
1150
- print_rest_exception(e, options)
1151
- exit 1
1152
- end
1153
- end
1154
-
1155
1512
  def remove(args)
1156
1513
  options = {}
1157
1514
  query_params = {keepBackups: 'off', force: 'off'}
@@ -1186,7 +1543,8 @@ class Morpheus::Cli::Instances
1186
1543
  print as_json(json_response, options), "\n"
1187
1544
  return
1188
1545
  elsif !options[:quiet]
1189
- list([])
1546
+ print_green_success "Removing instance #{instance['name']}"
1547
+ #list([])
1190
1548
  end
1191
1549
  rescue RestClient::Exception => e
1192
1550
  print_rest_exception(e, options)
@@ -1459,11 +1817,322 @@ class Morpheus::Cli::Instances
1459
1817
  end
1460
1818
  end
1461
1819
 
1462
- # check the instance
1463
- def check_status
1820
+
1821
+ def scaling(args)
1822
+ options = {}
1823
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
1824
+ opts.banner = subcommand_usage("[name]")
1825
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
1826
+ opts.footer = "Show scaling threshold information for an instance."
1827
+ end
1828
+ optparse.parse!(args)
1829
+ if args.count < 1
1830
+ print_error Morpheus::Terminal.angry_prompt
1831
+ puts_error "#{command_name} scaling requires argument [id or name list]\n#{optparse}"
1832
+ return 1
1833
+ end
1834
+ connect(options)
1835
+ id_list = parse_id_list(args)
1836
+ return run_command_for_each_arg(id_list) do |arg|
1837
+ _scaling(arg, options)
1838
+ end
1839
+ end
1840
+
1841
+ def _scaling(arg, options)
1842
+ instance = find_instance_by_name_or_id(arg)
1843
+ return 1 if instance.nil?
1844
+ if options[:dry_run]
1845
+ print_dry_run @instances_interface.dry.threshold(instance['id'], params)
1846
+ return 0
1847
+ end
1848
+ json_response = @instances_interface.threshold(instance['id'])
1849
+ if options[:include_fields]
1850
+ json_response = {"instanceThreshold" => filter_data(json_response["instanceThreshold"], options[:include_fields]) }
1851
+ end
1852
+ if options[:json]
1853
+ puts as_json(json_response, options)
1854
+ return 0
1855
+ elsif options[:yaml]
1856
+ puts as_yaml(json_response, options)
1857
+ return 0
1858
+ elsif options[:csv]
1859
+ puts records_as_csv([json_response['instanceThreshold']], options)
1860
+ return 0
1861
+ end
1862
+
1863
+ instance_threshold = json_response['instanceThreshold']
1864
+
1865
+ title = "Instance Scaling: [#{instance['id']}] #{instance['name']} (#{instance['instanceType']['name']})"
1866
+ print_h1 title
1867
+ if instance_threshold.empty?
1868
+ print yellow,"No scaling settings applied to this instance.",reset,"\n"
1869
+ else
1870
+ # print_h1 "Threshold Settings"
1871
+ print cyan
1872
+ print_instance_threshold_description_list(instance_threshold)
1873
+ end
1874
+ print reset, "\n"
1875
+ return 0
1876
+
1877
+ end
1878
+
1879
+ def scaling_update(args)
1880
+ usage = "Usage: morpheus instances scaling-update [name] [options]"
1881
+ options = {}
1882
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
1883
+ opts.banner = subcommand_usage("[name]")
1884
+ build_option_type_options(opts, options, instance_scaling_option_types(nil))
1885
+ build_common_options(opts, options, [:options, :json, :dry_run, :remote])
1886
+ opts.footer = "Update scaling threshold information for an instance."
1887
+ end
1888
+ optparse.parse!(args)
1889
+ # if args.count < 1
1890
+ if args.count != 1
1891
+ print_error Morpheus::Terminal.angry_prompt
1892
+ puts_error "#{command_name} scaling-update requires only one argument [id or name]\n#{optparse}"
1893
+ return 1
1894
+ end
1895
+ connect(options)
1896
+
1897
+ begin
1898
+
1899
+ instance = find_instance_by_name_or_id(args[0])
1900
+ return 1 if instance.nil?
1901
+ instance_threshold = @instances_interface.threshold(instance['id'])['instanceThreshold'] || {}
1902
+ my_option_types = instance_scaling_option_types(instance)
1903
+
1904
+ # preserve current values by setting the prompt options defaultValue attribute
1905
+ # note: checkbox type converts true,false to 'on','off'
1906
+ my_option_types.each do |opt|
1907
+ field_key = opt['fieldName'] # .sub('instanceThreshold.', '')
1908
+ if instance_threshold[field_key] != nil
1909
+ opt['defaultValue'] = instance_threshold[field_key]
1910
+ end
1911
+ end
1912
+
1913
+ # params = Morpheus::Cli::OptionTypes.prompt(my_option_types, options[:options], @api_client, {})
1914
+
1915
+ # ok, gotta split these inputs into sections with conditional logic
1916
+ params = {}
1917
+
1918
+ option_types_group = my_option_types.select {|opt| ['autoUp', 'autoDown'].include?(opt['fieldName']) }
1919
+ params.merge! Morpheus::Cli::OptionTypes.prompt(option_types_group, options[:options], @api_client, {})
1920
+
1921
+ option_types_group = my_option_types.select {|opt| ['zoneId'].include?(opt['fieldName']) }
1922
+ params.merge! Morpheus::Cli::OptionTypes.prompt(option_types_group, options[:options], @api_client, {})
1923
+ if params['zoneId']
1924
+ if params['zoneId'] == '' || params['zoneId'] == 'null' || params['zoneId'].to_s == '0'
1925
+ params['zoneId'] = 0
1926
+ else
1927
+ params['zoneId'] = params['zoneId'].to_i
1928
+ end
1929
+ end
1930
+
1931
+ option_types_group = my_option_types.select {|opt| ['minCount', 'maxCount'].include?(opt['fieldName']) }
1932
+ params.merge! Morpheus::Cli::OptionTypes.prompt(option_types_group, options[:options], @api_client, {})
1933
+
1934
+ option_types_group = my_option_types.select {|opt| ['memoryEnabled'].include?(opt['fieldName']) }
1935
+ params.merge! Morpheus::Cli::OptionTypes.prompt(option_types_group, options[:options], @api_client, {})
1936
+ if params['memoryEnabled'] == 'on' || params['memoryEnabled'] == true
1937
+ option_types_group = my_option_types.select {|opt| ['minMemory', 'maxMemory'].include?(opt['fieldName']) }
1938
+ params.merge! Morpheus::Cli::OptionTypes.prompt(option_types_group, options[:options], @api_client, {})
1939
+ else
1940
+ params['minMemory'] = nil
1941
+ params['maxMemory'] = nil
1942
+ end
1943
+
1944
+ option_types_group = my_option_types.select {|opt| ['diskEnabled'].include?(opt['fieldName']) }
1945
+ params.merge! Morpheus::Cli::OptionTypes.prompt(option_types_group, options[:options], @api_client, {})
1946
+ if params['diskEnabled'] == 'on' || params['diskEnabled'] == true
1947
+ option_types_group = my_option_types.select {|opt| ['minDisk', 'maxDisk'].include?(opt['fieldName']) }
1948
+ params.merge! Morpheus::Cli::OptionTypes.prompt(option_types_group, options[:options], @api_client, {})
1949
+ else
1950
+ params['minDisk'] = nil
1951
+ params['maxDisk'] = nil
1952
+ end
1953
+
1954
+ option_types_group = my_option_types.select {|opt| ['cpuEnabled'].include?(opt['fieldName']) }
1955
+ params.merge! Morpheus::Cli::OptionTypes.prompt(option_types_group, options[:options], @api_client, {})
1956
+ if params['cpuEnabled'] == 'on' || params['cpuEnabled'] == true
1957
+ option_types_group = my_option_types.select {|opt| ['minCpu', 'maxCpu'].include?(opt['fieldName']) }
1958
+ params.merge! Morpheus::Cli::OptionTypes.prompt(option_types_group, options[:options], @api_client, {})
1959
+ else
1960
+ params['minCpu'] = nil
1961
+ params['maxCpu'] = nil
1962
+ end
1963
+
1964
+ # argh, convert on/off to true/false
1965
+ # this needs a global solution...
1966
+ params.each do |k,v|
1967
+ if v == 'on' || v == 'true' || v == 'yes'
1968
+ params[k] = true
1969
+ elsif v == 'off' || v == 'false' || v == 'no'
1970
+ params[k] = false
1971
+ end
1972
+ end
1973
+
1974
+ payload = {
1975
+ 'instanceThreshold' => {}
1976
+ }
1977
+ payload['instanceThreshold'].merge!(params)
1978
+
1979
+ # unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to update the scaling settings for instance '#{instance['name']}'?", options)
1980
+ # return 9, "aborted command"
1981
+ # end
1982
+
1983
+ if options[:dry_run]
1984
+ print_dry_run @instances_interface.dry.update_threshold(instance['id'], payload)
1985
+ return
1986
+ end
1987
+ json_response = @instances_interface.update_threshold(instance['id'], payload)
1988
+ if options[:json]
1989
+ puts as_json(json_response, options)
1990
+ else
1991
+ print_green_success "Updated scaling settings for instance #{instance['name']}"
1992
+ end
1993
+ return 0
1994
+ rescue RestClient::Exception => e
1995
+ print_rest_exception(e, options)
1996
+ exit 1
1997
+ end
1998
+ end
1999
+
2000
+ def load_balancer_update(args)
2001
+ raise "Not Yet Implemented"
2002
+ usage = "Usage: morpheus instances lb-update [name] [options]"
2003
+ options = {}
2004
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
2005
+ opts.banner = subcommand_usage("[name]")
2006
+ #build_option_type_options(opts, options, instance_load_balancer_option_types(nil))
2007
+ build_common_options(opts, options, [:options, :json, :dry_run, :remote])
2008
+ opts.footer = "Assign a load balancer for an instance."
2009
+ end
2010
+ optparse.parse!(args)
2011
+ # if args.count < 1
2012
+ if args.count != 1
2013
+ print_error Morpheus::Terminal.angry_prompt
2014
+ puts_error "#{command_name} lb-update requires only one argument [id or name]\n#{optparse}"
2015
+ return 1
2016
+ end
2017
+ connect(options)
2018
+
2019
+ begin
2020
+
2021
+ instance = find_instance_by_name_or_id(args[0])
2022
+ return 1 if instance.nil?
2023
+ # refetch to get loadBalancers from show()
2024
+ json_response = @instances_interface.get(instance['id'])
2025
+
2026
+ current_instance_lb = nil
2027
+ # refetch to get current load balancer info from show()
2028
+ json_response = @instances_interface.get(instance['id'])
2029
+ #load_balancers = @instances_interface.threshold(instance['id'])['loadBalancers'] || {}
2030
+ if json_response['loadBalancers'] && json_response['loadBalancers'][0] && json_response['loadBalancers'][0]['lbs'] && json_response['loadBalancers'][0]['lbs'][0]
2031
+ current_instance_lb = json_response['loadBalancers'][0]['lbs'][0]
2032
+ #current_load_balancer = current_instance_lb['loadBalancer']
2033
+ #current_load_balancer_port = current_instance_lb['port']
2034
+ end
2035
+
2036
+ #my_option_types = instance_load_balancer_option_types(instance)
2037
+
2038
+ # todo...
2039
+
2040
+ # Host Name
2041
+ # Load Balancer
2042
+ # Protocol
2043
+ # Port
2044
+ # SSL Cert
2045
+ # Scheme
2046
+
2047
+ current_instance_lb = json_response['loadBalancers'][0]['lbs'][0]
2048
+
2049
+ params = {}
2050
+
2051
+ payload = {
2052
+ 'instance' => {},
2053
+ 'networkLoadBalancer' => {}
2054
+ }
2055
+
2056
+ cur_host_name = instance['hostName']
2057
+ #host_name = params = Morpheus::Cli::OptionTypes.prompt([{fieldName:'hostName'}], options[:options], @api_client, {})
2058
+ payload['instance']['hostName'] = instance['hostName']
2059
+
2060
+ payload['loadBalancerId'] = 9999
2061
+
2062
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to update the load balancer for instance '#{instance['name']}'?", options)
2063
+ return 9, "aborted command"
2064
+ end
2065
+
2066
+ if options[:dry_run]
2067
+ print_dry_run @instances_interface.dry.update_threshold(instance['id'], payload)
2068
+ return
2069
+ end
2070
+ json_response = @instances_interface.update_threshold(instance['id'], payload)
2071
+ if options[:json]
2072
+ puts as_json(json_response, options)
2073
+ else
2074
+ print_green_success "Updated scaling settings for instance #{instance['name']}"
2075
+ end
2076
+ return 0
2077
+ rescue RestClient::Exception => e
2078
+ print_rest_exception(e, options)
2079
+ exit 1
2080
+ end
2081
+ end
2082
+
2083
+ def load_balancer_remove(args)
2084
+ usage = "Usage: morpheus instances lb-remove [name] [options]"
2085
+ options = {}
2086
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
2087
+ opts.banner = subcommand_usage("[name]")
2088
+ build_option_type_options(opts, options, instance_scaling_option_types(nil))
2089
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
2090
+ opts.footer = "Remove a load balancer from an instance."
2091
+ end
2092
+ optparse.parse!(args)
2093
+ # if args.count < 1
2094
+ if args.count != 1
2095
+ print_error Morpheus::Terminal.angry_prompt
2096
+ puts_error "#{command_name} lb-remove requires only one argument [id or name]\n#{optparse}"
2097
+ return 1
2098
+ end
2099
+ connect(options)
2100
+
2101
+ begin
2102
+
2103
+ instance = find_instance_by_name_or_id(args[0])
2104
+ return 1 if instance.nil?
2105
+
2106
+ # re-fetch via show() get loadBalancers
2107
+ json_response = @instances_interface.get(instance['id'])
2108
+ load_balancers = json_response['instance']['loadBalancers']
2109
+
2110
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the load balancer for instance '#{instance['name']}'?", options)
2111
+ return 9, "aborted command"
2112
+ end
2113
+
2114
+ # no options here, just send DELETE request
2115
+ payload = {}
2116
+
2117
+ if options[:dry_run]
2118
+ print_dry_run @instances_interface.dry.remove_load_balancer(instance['id'], payload)
2119
+ return
2120
+ end
2121
+ json_response = @instances_interface.remove_load_balancer(instance['id'], payload)
2122
+ if options[:json]
2123
+ puts as_json(json_response, options)
2124
+ else
2125
+ print_green_success "Removed load balancer from instance #{instance['name']}"
2126
+ end
2127
+ return 0
2128
+ rescue RestClient::Exception => e
2129
+ print_rest_exception(e, options)
2130
+ exit 1
2131
+ end
1464
2132
  end
1465
2133
 
1466
- private
2134
+ private
2135
+
1467
2136
  def find_zone_by_name_or_id(group_id, val)
1468
2137
  zone = nil
1469
2138
  if val.to_s =~ /\A\d{1,}\Z/
@@ -1516,7 +2185,7 @@ class Morpheus::Cli::Instances
1516
2185
 
1517
2186
  def format_instance_status(instance, return_color=cyan)
1518
2187
  out = ""
1519
- status_string = instance['status']
2188
+ status_string = instance['status'].to_s
1520
2189
  if status_string == 'running'
1521
2190
  out << "#{green}#{status_string.upcase}#{return_color}"
1522
2191
  elsif status_string == 'stopped' or status_string == 'failed'
@@ -1535,10 +2204,127 @@ class Morpheus::Cli::Instances
1535
2204
  end
1536
2205
  end
1537
2206
 
2207
+ def format_container_status(container, return_color=cyan)
2208
+ out = ""
2209
+ status_string = container['status'].to_s
2210
+ if status_string == 'running'
2211
+ out << "#{green}#{status_string.upcase}#{return_color}"
2212
+ elsif status_string == 'stopped' or status_string == 'failed'
2213
+ out << "#{red}#{status_string.upcase}#{return_color}"
2214
+ elsif status_string == 'unknown'
2215
+ out << "#{white}#{status_string.upcase}#{return_color}"
2216
+ else
2217
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
2218
+ end
2219
+ out
2220
+ end
2221
+
2222
+ def format_container_connection_string(container)
2223
+ if !container['ports'].nil? && container['ports'].empty? == false
2224
+ connection_string = "#{container['ip']}:#{container['ports'][0]['external']}"
2225
+ else
2226
+ # eh? more logic needed here i think, see taglib morph:containerLocationMenu
2227
+ connection_string = "#{container['ip']}"
2228
+ end
2229
+ end
2230
+
1538
2231
  def clone_instance_option_types(connected=true)
1539
2232
  [
1540
2233
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Enter a name for the new instance'},
1541
2234
  {'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'selectOptions' => (connected ? get_available_groups() : []), 'required' => true},
1542
2235
  ]
1543
2236
  end
2237
+
2238
+ def instance_scaling_option_types(instance=nil)
2239
+
2240
+ # Group
2241
+ group_id = nil
2242
+ if instance && instance['group']
2243
+ group_id = instance['group']['id']
2244
+ end
2245
+
2246
+ available_clouds = group_id ? get_available_clouds(group_id) : []
2247
+ zone_dropdown = [{'name' => 'Use Scale Priority', 'value' => 0}]
2248
+ zone_dropdown += available_clouds.collect {|cloud| {'name' => cloud['name'], 'value' => cloud['id']} }
2249
+
2250
+ list = []
2251
+ list << {'fieldName' => 'autoUp', 'fieldLabel' => 'Auto Upscale', 'type' => 'checkbox', 'description' => 'Enable auto upscaling', 'required' => true, 'defaultValue' => false}
2252
+ list << {'fieldName' => 'autoDown', 'fieldLabel' => 'Auto Downscale', 'type' => 'checkbox', 'description' => 'Enable auto downscaling', 'required' => true, 'defaultValue' => false}
2253
+
2254
+ list << {'fieldName' => 'zoneId', 'fieldLabel' => 'Cloud', 'type' => 'select', 'selectOptions' => zone_dropdown, 'description' => "Choose a cloud to scale into.", 'placeHolder' => 'ID'}
2255
+
2256
+ list << {'fieldName' => 'minCount', 'fieldLabel' => 'Min Count', 'type' => 'number', 'description' => 'Minimum number of nodes', 'placeHolder' => 'NUMBER'}
2257
+ list << {'fieldName' => 'maxCount', 'fieldLabel' => 'Max Count', 'type' => 'number', 'description' => 'Maximum number of nodes', 'placeHolder' => 'NUMBER'}
2258
+
2259
+
2260
+ list << {'fieldName' => 'memoryEnabled', 'fieldLabel' => 'Enable Memory Threshold', 'type' => 'checkbox', 'description' => 'Scale when memory thresholds are met.', 'required' => true, 'defaultValue' => false}
2261
+ list << {'fieldName' => 'minMemory', 'fieldLabel' => 'Min Memory', 'type' => 'number', 'description' => 'Minimum memory percent (0-100)', 'placeHolder' => 'PERCENT'}
2262
+ list << {'fieldName' => 'maxMemory', 'fieldLabel' => 'Max Memory', 'type' => 'number', 'description' => 'Maximum memory percent (0-100)', 'placeHolder' => 'PERCENT'}
2263
+
2264
+ list << {'fieldName' => 'diskEnabled', 'fieldLabel' => 'Enable Disk Threshold', 'type' => 'checkbox', 'description' => 'Scale when disk thresholds are met.', 'required' => true, 'defaultValue' => false}
2265
+ list << {'fieldName' => 'minDisk', 'fieldLabel' => 'Min Disk', 'type' => 'number', 'description' => 'Minimum storage percent (0-100)', 'placeHolder' => 'PERCENT'}
2266
+ list << {'fieldName' => 'maxDisk', 'fieldLabel' => 'Max Disk', 'type' => 'number', 'description' => 'Maximum storage percent (0-100)', 'placeHolder' => 'PERCENT'}
2267
+
2268
+ list << {'fieldName' => 'cpuEnabled', 'fieldLabel' => 'Enable CPU Threshold', 'type' => 'checkbox', 'description' => 'Scale when cpu thresholds are met.', 'required' => true, 'defaultValue' => false}
2269
+ list << {'fieldName' => 'minCpu', 'fieldLabel' => 'Min CPU', 'type' => 'number', 'description' => 'Minimum CPU percent (0-100)', 'placeHolder' => 'PERCENT'}
2270
+ list << {'fieldName' => 'maxCpu', 'fieldLabel' => 'Max CPU', 'type' => 'number', 'description' => 'Maximum CPU percent (0-100)', 'placeHolder' => 'PERCENT'}
2271
+
2272
+ # list << {'fieldName' => 'iopsEnabled', 'fieldLabel' => 'Enable Iops Threshold', 'type' => 'checkbox', 'description' => 'Scale when iops thresholds are met.'}
2273
+ # list << {'fieldName' => 'minIops', 'fieldLabel' => 'Min Iops', 'type' => 'number', 'description' => 'Minimum iops'}
2274
+ # list << {'fieldName' => 'maxIops', 'fieldLabel' => 'Max Iops', 'type' => 'number', 'description' => 'Maximum iops'}
2275
+
2276
+ # list << {'fieldName' => 'networkEnabled', 'fieldLabel' => 'Enable Iops Threshold', 'type' => 'checkbox', 'description' => 'Scale when network thresholds are met.'}
2277
+ # list << {'fieldName' => 'minNetwork', 'fieldLabel' => 'Min Network', 'type' => 'number', 'description' => 'Minimum networking'}
2278
+
2279
+ # list << {'fieldName' => 'comment', 'fieldLabel' => 'Comment', 'type' => 'text', 'description' => 'Comment on these scaling settings.'}
2280
+
2281
+ list
2282
+ end
2283
+
2284
+ def instance_load_balancer_option_types(instance=nil)
2285
+ list = []
2286
+ list << {'fieldContext' => 'instance', 'fieldName' => 'hostName', 'fieldLabel' => 'Host Name', 'type' => 'checkbox', 'description' => 'Enable auto upscaling', 'required' => true, 'defaultValue' => instance ? instance['hostName'] : nil}
2287
+ list << {'fieldName' => 'proxyProtocol', 'fieldLabel' => 'Protocol', 'type' => 'checkbox', 'description' => 'Enable auto downscaling', 'required' => true, 'defaultValue' => false}
2288
+ list
2289
+ end
2290
+
2291
+ def format_instance_container_display_name(instance, plural=false)
2292
+ #<span class="info-label">${[null,'docker'].contains(instance.layout?.provisionType?.code) ? 'Containers' : 'Virtual Machines'}:</span> <span class="info-value">${instance.containers?.size()}</span>
2293
+ v = plural ? "Containers" : "Container"
2294
+ if instance && instance['layout'] && instance['layout'].key?("provisionTypeCode")
2295
+ if [nil, 'docker'].include?(instance['layout']["provisionTypeCode"])
2296
+ v = plural ? "Virtual Machines" : "Virtual Machine"
2297
+ end
2298
+ end
2299
+ return v
2300
+ end
2301
+
2302
+ def print_instance_threshold_description_list(instance_threshold)
2303
+ description_cols = {
2304
+ # "Instance" => lambda {|it| "#{instance['id']} - #{instance['name']}" },
2305
+ "Auto Upscale" => lambda {|it| format_boolean(it['autoUp']) },
2306
+ "Auto Downscale" => lambda {|it| format_boolean(it['autoDown']) },
2307
+ "Cloud" => lambda {|it| it['zoneId'] ? "#{it['zoneId']}" : 'Use Scale Priority' },
2308
+ "Min Count" => lambda {|it| it['minCount'] },
2309
+ "Max Count" => lambda {|it| it['maxCount'] },
2310
+ "Memory Enabled" => lambda {|it| format_boolean(it['memoryEnabled']) },
2311
+ "Min Memory" => lambda {|it| it['memoryEnabled'] ? (it['minMemory'] ? "#{it['minMemory']}%" : '') : '' },
2312
+ "Max Memory" => lambda {|it| it['memoryEnabled'] ? (it['maxMemory'] ? "#{it['maxMemory']}%" : '') : '' },
2313
+ "Disk Enabled" => lambda {|it| format_boolean(it['diskEnabled']) },
2314
+ "Min Disk" => lambda {|it| it['diskEnabled'] ? (it['minDisk'] ? "#{it['minDisk']}%" : '') : '' },
2315
+ "Max Disk" => lambda {|it| it['diskEnabled'] ? (it['maxDisk'] ? "#{it['maxDisk']}%" : '') : '' },
2316
+ "CPU Enabled" => lambda {|it| format_boolean(it['cpuEnabled']) },
2317
+ "Min CPU" => lambda {|it| it['cpuEnabled'] ? (it['minCpu'] ? "#{it['minCpu']}%" : '') : '' },
2318
+ "Max CPU" => lambda {|it| it['cpuEnabled'] ? (it['maxCpu'] ? "#{it['maxCpu']}%" : '') : '' },
2319
+ # "Iops Enabled" => lambda {|it| format_boolean(it['iopsEnabled']) },
2320
+ # "Min Iops" => lambda {|it| it['minIops'] },
2321
+ # "Max Iops" => lambda {|it| it['maxDisk'] },
2322
+ # "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
2323
+ "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
2324
+ }
2325
+ print_description_list(description_cols, instance_threshold)
2326
+ end
2327
+
2328
+
2329
+
1544
2330
  end