morpheus-cli 2.11.0 → 2.11.1

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