morpheus-cli 4.1.9 → 4.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07e484ff3233511b2a51a42df7cba277b96ac9f838166225b1b94af3229cba37
4
- data.tar.gz: 63af8a86ea3e86102a7270f7e6187a26750787a86c0fbf5345c8652271543883
3
+ metadata.gz: '090c9aa2420e68bbded20a8277e475990ce50a2b45adc63cb93d9d3f2fb3cf79'
4
+ data.tar.gz: e60d21d206cdf6de9a8658597e3f2bfa58cb193693306029df243755384247b5
5
5
  SHA512:
6
- metadata.gz: 7b9f0413f96cdc1da166407a814ca0d6de85554907e246f0f565b5b5b29b826b44785edab03056fd35b2751a02c4936033bc9de1b571d926509e4c489f38ef04
7
- data.tar.gz: 02e0b0c3a999fc1e2d1340df9bf2933e45897b41aa5bcc7b0c82146bc2f8d685548363f8ddb9aa6b0bf91dea35bd380a2cde144f02efda2c75bf8c777aae50f3
6
+ metadata.gz: eae9a2c5fea6a4da28738c130ed8d4087aece62b6770ec15a19ce8bfe845b05fe78be2ced93d62d9dacc2ae895359a6eebd126a7941b8cdb8ee546f2943b8f4b
7
+ data.tar.gz: dff2a2f2511f56b9f4da2b278f3cd49aacc7dea276b711a484c8448115abf3db9d743a0db2bf9ea7c5c9ce1aa5bff51fdcf83e604e3c8d2ce6b892dbe4eef3de
@@ -598,9 +598,12 @@ class Morpheus::APIClient
598
598
  Morpheus::LibraryContainerTemplatesInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
599
599
  end
600
600
 
601
+ def library_cluster_layouts
602
+ Morpheus::LibraryClusterLayoutsInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
603
+ end
601
604
 
602
- def library_compute_type_layouts
603
- Morpheus::LibraryComputeTypeLayoutsInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
605
+ def library_resource_specs
606
+ Morpheus::LibraryResourceSpecsInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
604
607
  end
605
608
 
606
609
  def packages
@@ -79,6 +79,13 @@ class Morpheus::AppsInterface < Morpheus::APIClient
79
79
  execute(opts)
80
80
  end
81
81
 
82
+ def cancel_removal(id, params = {})
83
+ url = "#{@base_url}/api/apps/#{id}/cancel-removal"
84
+ headers = {:params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
85
+ opts = {method: :put, url: url, headers: headers}
86
+ execute(opts)
87
+ end
88
+
82
89
  def stop(id)
83
90
  url = "#{@base_url}/api/apps/#{id}/stop"
84
91
  headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
@@ -68,6 +68,13 @@ class Morpheus::InstancesInterface < Morpheus::APIClient
68
68
  execute(opts)
69
69
  end
70
70
 
71
+ def cancel_removal(id, params = {})
72
+ url = "#{@base_url}/api/instances/#{id}/cancel-removal"
73
+ headers = {:params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
74
+ opts = {method: :put, url: url, headers: headers}
75
+ execute(opts)
76
+ end
77
+
71
78
  def stop(id, params={})
72
79
  url = "#{@base_url}/api/instances/#{id}/stop"
73
80
  headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
@@ -0,0 +1,53 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::LibraryClusterLayoutsInterface < Morpheus::APIClient
4
+ def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
5
+ @access_token = access_token
6
+ @refresh_token = refresh_token
7
+ @base_url = base_url
8
+ @expires_at = expires_at
9
+ end
10
+
11
+ def list(params={})
12
+ url = "#{@base_url}/api/library/cluster-layouts"
13
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
14
+ opts = {method: :get, url: url, headers: headers}
15
+ execute(opts)
16
+ end
17
+
18
+ def get(id, params={})
19
+ raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
20
+ url = "#{@base_url}/api/library/cluster-layouts/#{id}"
21
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
22
+ opts = {method: :get, url: url, headers: headers}
23
+ execute(opts)
24
+ end
25
+
26
+ def create(payload)
27
+ url = "#{@base_url}/api/library/cluster-layouts"
28
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
29
+ opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
30
+ execute(opts)
31
+ end
32
+
33
+ def update(id, payload)
34
+ url = "#{@base_url}/api/library/cluster-layouts/#{id}"
35
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
36
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
37
+ execute(opts)
38
+ end
39
+
40
+ def clone(id, params)
41
+ url = "#{@base_url}/api/library/cluster-layouts/#{id}/clone"
42
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json', params: params }
43
+ opts = {method: :post, url: url, headers: headers}
44
+ execute(opts)
45
+ end
46
+
47
+ def destroy(id, payload={})
48
+ url = "#{@base_url}/api/library/cluster-layouts/#{id}"
49
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
50
+ opts = {method: :delete, url: url, headers: headers, payload: payload.to_json}
51
+ execute(opts)
52
+ end
53
+ end
@@ -0,0 +1,49 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::LibraryResourceSpecsInterface < Morpheus::APIClient
4
+ def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
5
+ @access_token = access_token
6
+ @refresh_token = refresh_token
7
+ @base_url = base_url
8
+ @expires_at = expires_at
9
+ end
10
+
11
+ def get(id, params={})
12
+ raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
13
+ url = "#{@base_url}/api/library/spec-templates/#{id}"
14
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
15
+ opts = {method: :get, url: url, headers: headers}
16
+ execute(opts)
17
+ end
18
+
19
+ def list(params={})
20
+ url = "#{@base_url}/api/library/spec-templates"
21
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
22
+ opts = {method: :get, url: url, headers: headers}
23
+ execute(opts)
24
+ end
25
+
26
+ def create(options)
27
+ url = "#{@base_url}/api/library/spec-templates"
28
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
29
+ payload = options
30
+ opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
31
+ execute(opts)
32
+ end
33
+
34
+ def update(id, options)
35
+ url = "#{@base_url}/api/library/spec-templates/#{id}"
36
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
37
+ payload = options
38
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
39
+ execute(opts)
40
+ end
41
+
42
+ def destroy(id, payload={})
43
+ url = "#{@base_url}/api/library/spec-templates/#{id}"
44
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
45
+ opts = {method: :delete, url: url, headers: headers, payload: payload.to_json}
46
+ execute(opts)
47
+ end
48
+
49
+ end
@@ -114,6 +114,7 @@ module Morpheus
114
114
  load 'morpheus/cli/virtual_images.rb'
115
115
  # load 'morpheus/cli/library.rb' # gone until we collapse these again
116
116
  load 'morpheus/cli/library_instance_types_command.rb'
117
+ load 'morpheus/cli/library_cluster_layouts_command.rb'
117
118
  load 'morpheus/cli/library_layouts_command.rb'
118
119
  load 'morpheus/cli/library_upgrades_command.rb'
119
120
  load 'morpheus/cli/library_container_types_command.rb'
@@ -121,6 +122,7 @@ module Morpheus
121
122
  load 'morpheus/cli/library_container_templates_command.rb'
122
123
  load 'morpheus/cli/library_option_types_command.rb'
123
124
  load 'morpheus/cli/library_option_lists_command.rb'
125
+ load 'morpheus/cli/library_resource_specs_command.rb'
124
126
  load 'morpheus/cli/monitoring_incidents_command.rb'
125
127
  load 'morpheus/cli/monitoring_checks_command.rb'
126
128
  load 'morpheus/cli/monitoring_contacts_command.rb'
@@ -17,7 +17,7 @@ class Morpheus::Cli::Apps
17
17
  include Morpheus::Cli::LogsHelper
18
18
  set_command_name :apps
19
19
  set_command_description "View and manage apps."
20
- register_subcommands :list, :count, :get, :view, :add, :update, :remove, :add_instance, :remove_instance, :logs, :security_groups, :apply_security_groups, :history
20
+ register_subcommands :list, :count, :get, :view, :add, :update, :remove, :cancel_removal, :add_instance, :remove_instance, :logs, :security_groups, :apply_security_groups, :history
21
21
  register_subcommands :stop, :start, :restart
22
22
  register_subcommands :wiki, :update_wiki
23
23
  #register_subcommands :firewall_disable, :firewall_enable
@@ -60,6 +60,9 @@ class Morpheus::Cli::Apps
60
60
  opts.on('--details', "Display more details: memory and storage usage used / max values." ) do
61
61
  options[:details] = true
62
62
  end
63
+ opts.on('--pending-removal', "Include apps pending removal.") do
64
+ options[:pendingRemoval] = true
65
+ end
63
66
  build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
64
67
  opts.footer = "List apps."
65
68
  end
@@ -79,6 +82,9 @@ class Morpheus::Cli::Apps
79
82
  return if created_by_ids.nil?
80
83
  params['createdBy'] = created_by_ids
81
84
  end
85
+
86
+ params['showDeleted'] = true if options[:pendingRemoval]
87
+
82
88
  @apps_interface.setopts(options)
83
89
  if options[:dry_run]
84
90
  print_dry_run @apps_interface.dry.list(params)
@@ -636,6 +642,9 @@ class Morpheus::Cli::Apps
636
642
  },
637
643
  "Status" => lambda {|it| format_app_status(it) }
638
644
  }
645
+
646
+ description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if app['status'] == 'pendingRemoval'
647
+
639
648
  if app['blueprint'].nil?
640
649
  description_cols.delete("Blueprint")
641
650
  end
@@ -929,6 +938,38 @@ class Morpheus::Cli::Apps
929
938
  end
930
939
  end
931
940
 
941
+ def cancel_removal(args)
942
+ options = {}
943
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
944
+ opts.banner = subcommand_usage("[app]")
945
+ build_common_options(opts, options, [:json, :dry_run, :quiet, :remote])
946
+ end
947
+ optparse.parse!(args)
948
+ if args.count < 1
949
+ puts optparse
950
+ exit 1
951
+ end
952
+ connect(options)
953
+ begin
954
+ app = find_app_by_name_or_id(args[0])
955
+ @apps_interface.setopts(options)
956
+ if options[:dry_run]
957
+ print_dry_run @apps_interface.dry.cancel_removal(app['id'])
958
+ return
959
+ end
960
+ json_response = @apps_interface.cancel_removal(app['id'])
961
+ if options[:json]
962
+ print as_json(json_response, options), "\n"
963
+ return
964
+ elsif !options[:quiet]
965
+ get([app['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
966
+ end
967
+ rescue RestClient::Exception => e
968
+ print_rest_exception(e, options)
969
+ exit 1
970
+ end
971
+ end
972
+
932
973
  def remove_instance(args)
933
974
  options = {}
934
975
  optparse = Morpheus::Cli::OptionParser.new do |opts|
@@ -105,6 +105,24 @@ module Morpheus
105
105
  [id_list].flatten.collect {|it| it ? it.to_s.split(delim) : nil }.flatten.compact
106
106
  end
107
107
 
108
+ def parse_bytes_param(bytes_param, option, assumed_unit = nil)
109
+ if bytes_param && bytes_param.to_f > 0
110
+ bytes_param.upcase!
111
+ multiplier = 1
112
+ unit = nil
113
+ number = (bytes_param.to_f == bytes_param.to_i ? bytes_param.to_i : bytes_param.to_f)
114
+ if (bytes_param.end_with? 'GB') || ((!bytes_param.end_with? 'MB') && assumed_unit == 'GB')
115
+ unit = 'GB'
116
+ multiplier = 1024 * 1024 * 1024
117
+ elsif (bytes_param.end_with? 'MB') || assumed_unit == 'MB'
118
+ unit = 'MB'
119
+ multiplier = 1024 * 1024
120
+ end
121
+ return {:bytes_param => bytes_param, :bytes => number * multiplier, :number => number, :multiplier => multiplier, :unit => unit}
122
+ end
123
+ raise_command_error "Invalid value for #{option} option"
124
+ end
125
+
108
126
  # Appends Array of OptionType definitions to an OptionParser instance
109
127
  # This adds an option like --fieldContext.fieldName="VALUE"
110
128
  # @param opts [OptionParser]
@@ -33,7 +33,7 @@ class Morpheus::Cli::Clusters
33
33
  @api_client = establish_remote_appliance_connection(opts)
34
34
  @clusters_interface = @api_client.clusters
35
35
  @groups_interface = @api_client.groups
36
- @compute_type_layouts_interface = @api_client.library_compute_type_layouts
36
+ @cluster_layouts_interface = @api_client.library_cluster_layouts
37
37
  @security_groups_interface = @api_client.security_groups
38
38
  #@security_group_rules_interface = @api_client.security_group_rules
39
39
  @cloud_resource_pools_interface = @api_client.cloud_resource_pools
@@ -3605,15 +3605,15 @@ class Morpheus::Cli::Clusters
3605
3605
  end
3606
3606
 
3607
3607
  def find_layout_by_id(id)
3608
- @compute_type_layouts_interface.get(id)['layout'] rescue nil
3608
+ @cluster_layouts_interface.get(id)['layout'] rescue nil
3609
3609
  end
3610
3610
 
3611
3611
  def find_layout_by_name(name)
3612
- @compute_type_layouts_interface.list({phrase:name}).find { it['name'].downcase == name.downcase || it['code'].downcase == name.downcase }
3612
+ @cluster_layouts_interface.list({phrase:name}).find { it['name'].downcase == name.downcase || it['code'].downcase == name.downcase }
3613
3613
  end
3614
3614
 
3615
3615
  def layouts_for_dropdown(zone_id, group_type_id)
3616
- @compute_type_layouts_interface.list({zoneId: zone_id, groupTypeId: group_type_id})["layouts"].collect { |it| {'id' => it['id'], 'name' => it['name'], 'value' => it['id'], 'code' => it['code']} }
3616
+ @cluster_layouts_interface.list({zoneId: zone_id, groupTypeId: group_type_id})["layouts"].collect { |it| {'id' => it['id'], 'name' => it['name'], 'value' => it['id'], 'code' => it['code']} }
3617
3617
  end
3618
3618
 
3619
3619
  def find_service_plan_by_name_or_id(val)
@@ -17,7 +17,7 @@ class Morpheus::Cli::Instances
17
17
  include Morpheus::Cli::LogsHelper
18
18
  set_command_name :instances
19
19
  set_command_description "View and manage instances."
20
- register_subcommands :list, :count, :get, :view, :add, :update, :update_notes, :remove, :logs, :history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details}, :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, :run_workflow, :import_snapshot, :console, :status_check, {:containers => :list_containers}, :scaling, {:'scaling-update' => :scaling_update}
20
+ register_subcommands :list, :count, :get, :view, :add, :update, :update_notes, :remove, :cancel_removal, :logs, :history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details}, :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, :run_workflow, :import_snapshot, :console, :status_check, {:containers => :list_containers}, :scaling, {:'scaling-update' => :scaling_update}
21
21
  register_subcommands :wiki, :update_wiki
22
22
  register_subcommands :exec => :execution_request
23
23
  #register_subcommands :firewall_disable, :firewall_enable
@@ -71,6 +71,9 @@ class Morpheus::Cli::Instances
71
71
  opts.on('--details', "Display more details: memory and storage usage used / max values." ) do
72
72
  options[:details] = true
73
73
  end
74
+ opts.on('--pending-removal', "Include instances pending removal.") do
75
+ options[:pendingRemoval] = true
76
+ end
74
77
  build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
75
78
  opts.footer = "List instances."
76
79
  end
@@ -106,6 +109,8 @@ class Morpheus::Cli::Instances
106
109
  params['createdBy'] = created_by_ids
107
110
  end
108
111
 
112
+ params['showDeleted'] = true if options[:pendingRemoval]
113
+
109
114
  @instances_interface.setopts(options)
110
115
  if options[:dry_run]
111
116
  print_dry_run @instances_interface.dry.list(params)
@@ -1272,6 +1277,9 @@ class Morpheus::Cli::Instances
1272
1277
  #"Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
1273
1278
  "Status" => lambda {|it| format_instance_status(it) }
1274
1279
  }
1280
+
1281
+ description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if instance['status'] == 'pendingRemoval'
1282
+
1275
1283
  print_description_list(description_cols, instance)
1276
1284
 
1277
1285
  if instance['statusMessage']
@@ -2593,7 +2601,6 @@ class Morpheus::Cli::Instances
2593
2601
  query_params[:force] = 'on'
2594
2602
  end
2595
2603
  build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
2596
-
2597
2604
  end
2598
2605
  optparse.parse!(args)
2599
2606
  if args.count < 1
@@ -2629,6 +2636,38 @@ class Morpheus::Cli::Instances
2629
2636
  end
2630
2637
  end
2631
2638
 
2639
+ def cancel_removal(args)
2640
+ options = {}
2641
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2642
+ opts.banner = subcommand_usage("[instance]")
2643
+ build_common_options(opts, options, [:json, :dry_run, :quiet, :remote])
2644
+ end
2645
+ optparse.parse!(args)
2646
+ if args.count < 1
2647
+ puts optparse
2648
+ exit 1
2649
+ end
2650
+ connect(options)
2651
+ begin
2652
+ instance = find_instance_by_name_or_id(args[0])
2653
+ @instances_interface.setopts(options)
2654
+ if options[:dry_run]
2655
+ print_dry_run @instances_interface.dry.cancel_removal(instance['id'])
2656
+ return
2657
+ end
2658
+ json_response = @instances_interface.cancel_removal(instance['id'])
2659
+ if options[:json]
2660
+ print as_json(json_response, options), "\n"
2661
+ return
2662
+ elsif !options[:quiet]
2663
+ get([instance['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
2664
+ end
2665
+ rescue RestClient::Exception => e
2666
+ print_rest_exception(e, options)
2667
+ exit 1
2668
+ end
2669
+ end
2670
+
2632
2671
  def firewall_disable(args)
2633
2672
  options = {}
2634
2673
  optparse = Morpheus::Cli::OptionParser.new do |opts|
@@ -0,0 +1,863 @@
1
+ require 'io/console'
2
+ require 'optparse'
3
+ require 'filesize'
4
+ require 'morpheus/cli/cli_command'
5
+ require 'morpheus/cli/mixins/library_helper'
6
+
7
+ class Morpheus::Cli::LibraryClusterLayoutsCommand
8
+ include Morpheus::Cli::CliCommand
9
+ include Morpheus::Cli::LibraryHelper
10
+
11
+ set_command_name :'library-cluster-layouts'
12
+
13
+ register_subcommands :list, :get, :add, :update, :remove, :clone
14
+
15
+ def initialize()
16
+ end
17
+
18
+ def connect(opts)
19
+ @api_client = establish_remote_appliance_connection(opts)
20
+ @library_cluster_layouts_interface = @api_client.library_cluster_layouts
21
+ @library_container_types_interface = @api_client.library_container_types
22
+ @clusters_interface = @api_client.clusters
23
+ @provision_types_interface = @api_client.provision_types
24
+ @options_types_interface = @api_client.option_types
25
+ @task_sets_interface = @api_client.task_sets
26
+ end
27
+
28
+ def handle(args)
29
+ handle_subcommand(args)
30
+ end
31
+
32
+ def list(args)
33
+ options = {}
34
+ params = {}
35
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
36
+ opts.banner = subcommand_usage()
37
+ opts.on('--technology VALUE', String, "Filter by technology") do |val|
38
+ params['provisionType'] = val
39
+ end
40
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
41
+ opts.footer = "List cluster layouts."
42
+ end
43
+ optparse.parse!(args)
44
+ if args.count > 0
45
+ print_error Morpheus::Terminal.angry_prompt
46
+ puts_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.inspect}\n#{optparse}"
47
+ return 1
48
+ end
49
+ connect(options)
50
+ begin
51
+ # construct payload
52
+ params.merge!(parse_list_options(options))
53
+ @library_cluster_layouts_interface.setopts(options)
54
+ if options[:dry_run]
55
+ print_dry_run @library_cluster_layouts_interface.dry.list(params)
56
+ return
57
+ end
58
+ # do it
59
+ json_response = @library_cluster_layouts_interface.list(params)
60
+ # print and/or return result
61
+ # return 0 if options[:quiet]
62
+ if options[:json]
63
+ puts as_json(json_response, options, "layouts")
64
+ return 0
65
+ elsif options[:csv]
66
+ puts records_as_csv(json_response['layouts'], options)
67
+ return 0
68
+ elsif options[:yaml]
69
+ puts as_yaml(json_response, options, "layouts")
70
+ return 0
71
+ end
72
+ layouts = json_response['layouts']
73
+ title = "Morpheus Library - Cluster Layout"
74
+ subtitles = parse_list_subtitles(options)
75
+ print_h1 title, subtitles
76
+ if layouts.empty?
77
+ print cyan,"No cluster layouts found.",reset,"\n"
78
+ else
79
+ rows = layouts.collect do |layout|
80
+ {
81
+ id: layout['id'],
82
+ name: layout['name'],
83
+ cloud_type: layout_cloud_type(layout),
84
+ version: layout['computeVersion'],
85
+ description: layout['description']
86
+ }
87
+ end
88
+ print as_pretty_table(rows, [:id, :name, :cloud_type, :version, :description], options)
89
+ print_results_pagination(json_response, {:label => "node type", :n_label => "node types"})
90
+ end
91
+ print reset,"\n"
92
+ rescue RestClient::Exception => e
93
+ print_rest_exception(e, options)
94
+ return 1
95
+ end
96
+ end
97
+
98
+ def get(args)
99
+ options = {}
100
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
101
+ opts.banner = subcommand_usage("[layout]")
102
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
103
+ opts.footer = "Display cluster layout details." + "\n" +
104
+ "[layout] is required. This is the name or id of a cluster layout."
105
+ end
106
+ optparse.parse!(args)
107
+ if args.count < 1
108
+ puts optparse
109
+ return 1
110
+ end
111
+ connect(options)
112
+ id_list = parse_id_list(args)
113
+ id_list.each do |id|
114
+
115
+ end
116
+ return run_command_for_each_arg(id_list) do |arg|
117
+ _get(arg, options)
118
+ end
119
+ end
120
+
121
+ def _get(id, options)
122
+ begin
123
+ @library_cluster_layouts_interface.setopts(options)
124
+ if options[:dry_run]
125
+ if arg.to_s =~ /\A\d{1,}\Z/
126
+ print_dry_run @library_cluster_layouts_interface.dry.get(arg.to_i)
127
+ else
128
+ print_dry_run @library_container_types_interface.dry.list({name:arg})
129
+ end
130
+ return
131
+ end
132
+ layout = find_layout_by_name_or_id(id)
133
+ if layout.nil?
134
+ return 1
135
+ end
136
+
137
+ json_response = {'layout' => layout}
138
+
139
+ if options[:json]
140
+ puts as_json(json_response, options, "layout")
141
+ return 0
142
+ elsif options[:yaml]
143
+ puts as_yaml(json_response, options, "layout")
144
+ return 0
145
+ elsif options[:csv]
146
+ puts records_as_csv([json_response['layout']], options)
147
+ return 0
148
+ end
149
+
150
+ print_h1 "Cluster Layout Details"
151
+ print cyan
152
+ description_cols = {
153
+ "ID" => lambda {|it| it['id'] },
154
+ "Name" => lambda {|it| it['name'] },
155
+ "Version" => lambda {|it| it['computeVersion']},
156
+ # "Type" => lambda {|it| it['type'] ? it['type']['name'] : nil},
157
+ "Creatable" => lambda {|it| format_boolean(it['creatable'])},
158
+ "Cloud Type" => lambda {|it| layout_cloud_type(it)},
159
+ "Cluster Type" => lambda {|it| it['groupType'] ? it['groupType']['name'] : nil},
160
+ "Technology" => lambda {|it| it['provisionType'] ? it['provisionType']['code'] : nil},
161
+ "Minimum Memory" => lambda {|it| printable_byte_size(it['memoryRequirement'])},
162
+ "Workflow" => lambda {|it| it['taskSets'] && it['taskSets'].count > 0 ? it['taskSets'][0]['name'] : nil},
163
+ "Description" => lambda {|it| it['description']},
164
+ "Horizontal Scaling" => lambda {|it| format_boolean(it['hasAutoScale'])},
165
+ }
166
+
167
+ print_description_list(description_cols, layout)
168
+
169
+ if (layout['environmentVariables'] || []).count > 0
170
+ rows = layout['environmentVariables'].collect do |evar|
171
+ {
172
+ name: evar['name'],
173
+ value: evar['defaultValue'],
174
+ masked: format_boolean(evar['masked']),
175
+ label: format_boolean(evar['export'])
176
+ }
177
+ end
178
+ print_h2 "Environment Variables"
179
+ puts as_pretty_table(rows, [:name, :value, :masked, :label])
180
+ end
181
+
182
+ if (layout['optionTypes'] || []).count > 0
183
+ rows = layout['optionTypes'].collect do |opt|
184
+ {
185
+ label: opt['fieldLabel'],
186
+ type: opt['type']
187
+ }
188
+ end
189
+ print_h2 "Option Types"
190
+ puts as_pretty_table(rows, [:label, :type])
191
+ end
192
+
193
+ ['master', 'worker'].each do |node_type|
194
+ nodes = layout['computeServers'].reject {|it| it['nodeType'] != node_type}.collect do |server|
195
+ container = server['containerType']
196
+ {
197
+ id: container['id'],
198
+ name: container['name'],
199
+ short_name: container['shortName'],
200
+ version: container['containerVersion'],
201
+ category: container['category'],
202
+ count: server['nodeCount']
203
+ }
204
+ end
205
+
206
+ if nodes.count > 0
207
+ print_h2 "#{node_type.capitalize} Nodes"
208
+ puts as_pretty_table(nodes, [:id, :name, :short_name, :version, :category, :count])
209
+ end
210
+ end
211
+ print reset,"\n"
212
+ rescue RestClient::Exception => e
213
+ print_rest_exception(e, options)
214
+ return 1
215
+ end
216
+ end
217
+
218
+ def add(args)
219
+ options = {}
220
+ params = {}
221
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
222
+ opts.banner = subcommand_usage("[name] [options]")
223
+ opts.on('-n', '--name VALUE', String, "Name for this cluster layout") do |val|
224
+ params['name'] = val
225
+ end
226
+ opts.on('-D', '--description VALUE', String, "Description") do |val|
227
+ params['description'] = val
228
+ end
229
+ opts.on('-v', '--version VALUE', String, "Version") do |val|
230
+ params['computeVersion'] = val
231
+ end
232
+ opts.on('-c', '--creatable [on|off]', String, "Can be used to enable / disable creatable layout. Default is on") do |val|
233
+ params['creatable'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
234
+ end
235
+ opts.on('-g', '--cluster-type CODE', String, "Cluster type. This is the cluster type code.") do |val|
236
+ options[:clusterTypeCode] = val
237
+ end
238
+ opts.on('-t', '--technology CODE', String, "Technology. This is the provision type code.") do |val|
239
+ options[:provisionTypeCode] = val
240
+ end
241
+ opts.on('-m', '--min-memory NUMBER', String, "Min memory. Assumes MB unless optional modifier specified, ex: 1GB") do |val|
242
+ bytes = parse_bytes_param(val, '--min-memory', 'MB')
243
+ params['memoryRequirement'] = bytes[:bytes]
244
+ end
245
+ opts.on('-w', '--workflow ID', String, "Workflow") do |val|
246
+ options[:taskSetId] = val.to_i
247
+ end
248
+ opts.on('-s', '--auto-scale [on|off]', String, "Can be used to enable / disable horizontal scaling. Default is on") do |val|
249
+ params['hasAutoScale'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
250
+ end
251
+ opts.on('--evars-json JSON', String, 'Environment variables JSON: {"name":"Foo", "value":"Bar", "masked":true, "export":true}' ) do |val|
252
+ begin
253
+ evars = JSON.parse(val.to_s)
254
+ params['environmentVariables'] = evars.kind_of?(Array) ? evars : [evars]
255
+ rescue JSON::ParserError => e
256
+ print_red_alert "Unable to parse evars JSON"
257
+ exit 1
258
+ end
259
+ end
260
+ opts.on('-e', '--evars LIST', Array, "Environment variables list. Comma delimited list of name=value pairs") do |val|
261
+ params['environmentVariables'] = val.collect do |nv|
262
+ parts = nv.split('=')
263
+ {'name' => parts[0].strip, 'value' => (parts.count > 1 ? parts[1].strip : '')}
264
+ end
265
+ end
266
+ opts.on('-o', '--option-types LIST', Array, "Option types, comma separated list of option type IDs") do |val|
267
+ options[:optionTypes] = val
268
+ end
269
+ opts.on('--masters LIST', Array, "List of master. Comma separated container types IDs in format id[/count], ex: 100,101/3") do |val|
270
+ options[:masters] = val
271
+ end
272
+ opts.on('--workers LIST', Array, "List of workers. Comma separated container types IDs in format id[/count], ex: 100,101/3") do |val|
273
+ options[:workers] = val
274
+ end
275
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
276
+ opts.footer = "Create a cluster layout."
277
+ end
278
+ optparse.parse!(args)
279
+ connect(options)
280
+ if args.count > 1
281
+ print_error Morpheus::Terminal.angry_prompt
282
+ puts_error "wrong number of arguments, expected 0-1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
283
+ return 1
284
+ end
285
+ if args[0]
286
+ params['name'] = args[0]
287
+ end
288
+ begin
289
+ if options[:payload]
290
+ payload = options[:payload]
291
+ else
292
+ # support the old -O OPTION switch
293
+ params.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
294
+
295
+ # prompt for options
296
+ if params['name'].nil?
297
+ params['name'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'text', 'fieldLabel' => 'Name', 'required' => true}], options[:options], @api_client,{})['value']
298
+ end
299
+
300
+ # version
301
+ if params['computeVersion'].nil?
302
+ params['computeVersion'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'text', 'fieldLabel' => 'Version', 'required' => true}], options[:options], @api_client,{})['value']
303
+ end
304
+
305
+ # description
306
+ if params['description'].nil?
307
+ params['description'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'text', 'fieldLabel' => 'Description', 'required' => false}], options[:options], @api_client,{})['value']
308
+ end
309
+
310
+ # creatable
311
+ if params['creatable'].nil?
312
+ params['creatable'] = Morpheus::Cli::OptionTypes.confirm("Creatable?", {:default => true}) == true
313
+ end
314
+
315
+ # cluster type
316
+ if options[:clusterTypeCode]
317
+ cluster_type = find_cluster_type_by_code(options[:clusterTypeCode])
318
+ if cluster_type.nil?
319
+ print_red_alert "Cluster type #{options[:clusterTypeCode]} not found"
320
+ exit 1
321
+ end
322
+ else
323
+ cluster_type_options = cluster_types.collect {|type| {'name' => type['name'], 'value' => type['code']}}
324
+ cluster_type_code = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Cluster Type', 'required' => true, 'selectOptions' => cluster_type_options}], options[:options], @api_client,{}, nil, true)['value']
325
+ cluster_type = cluster_types.find {|type| type['code'] == cluster_type_code}
326
+ end
327
+
328
+ params['groupType'] = {'id' => cluster_type['id']}
329
+
330
+ # technology customSupported, createServer
331
+ if options[:provisionTypeCode]
332
+ provision_type = find_provision_type_by_code(options[:provisionTypeCode])
333
+ if provision_type.nil?
334
+ print_red_alert "Technology #{options[:provisionTypeCode]} not found"
335
+ exit 1
336
+ end
337
+ else
338
+ provision_type_options = provision_types.collect {|type| {'name' => type['name'], 'value' => type['code']}}
339
+ provision_type_code = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Technology', 'required' => true, 'selectOptions' => provision_type_options}], options[:options], @api_client,{}, nil, true)['value']
340
+ provision_type = provision_types.find {|type| type['code'] == provision_type_code}
341
+ end
342
+
343
+ params['provisionType'] = {'id' => provision_type['id']}
344
+
345
+ # min memory
346
+ if params['memoryRequirement'].nil?
347
+ memory = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'text', 'fieldLabel' => 'Minimum Memory (MB) [can use GB modifier]', 'required' => false, 'description' => 'Memory (MB)'}],options[:options],@api_client,{}, options[:no_prompt])['value']
348
+
349
+ if memory
350
+ bytes = parse_bytes_param(memory, 'minimum memory', 'MB')
351
+ params['memoryRequirement'] = bytes[:bytes]
352
+ end
353
+ end
354
+
355
+ # workflow
356
+ if options[:taskSetId]
357
+ task_set = @task_sets_interface.get(options[:taskSetId])['taskSet']
358
+
359
+ if !task_set
360
+ print_red_alert "Workflow #{options[:taskSetId]} not found"
361
+ exit 1
362
+ end
363
+ params['taskSets'] = [{'id' => task_set['id']}]
364
+ else
365
+ task_set_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'fieldLabel' => 'Workflow', 'type' => 'select', 'required' => false, 'optionSource' => 'taskSets'}], {}, @api_client, {})['value']
366
+
367
+ if task_set_id
368
+ params['taskSets'] = [{'id' => task_set_id.to_i}]
369
+ end
370
+ end
371
+
372
+ # auto scale
373
+ if params['hasAutoScale'].nil?
374
+ params['hasAutoScale'] = Morpheus::Cli::OptionTypes.confirm("Enable scaling?", {:default => false}) == true
375
+ end
376
+
377
+ # evars?
378
+ if params['environmentVariables'].nil?
379
+ evars = []
380
+ while Morpheus::Cli::OptionTypes.confirm("Add #{evars.empty? ? '' : 'another '}environment variable?", {:default => false}) do
381
+ evars << prompt_evar(options)
382
+ end
383
+ params['environmentVariables'] = evars
384
+ end
385
+
386
+ # option types
387
+ if options[:optionTypes]
388
+ option_types = []
389
+ options[:optionTypes].each do |option_type_id|
390
+ if @options_types_interface.get(option_type_id.to_i).nil?
391
+ print_red_alert "Option type #{option_type_id} not found"
392
+ exit 1
393
+ else
394
+ option_types << {'id' => option_type_id.to_i}
395
+ end
396
+ end
397
+ elsif !options[:no_prompt]
398
+ avail_type_options = @options_types_interface.list({'max' => 1000})['optionTypes'].collect {|it| {'name' => it['name'], 'value' => it['id']}}
399
+ option_types = []
400
+ while !avail_type_options.empty? && Morpheus::Cli::OptionTypes.confirm("Add #{option_types.empty? ? '' : 'another '}option type?", {:default => false}) do
401
+ option_type_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Option Type', 'selectOptions' => avail_type_options, 'required' => false}],options[:options],@api_client,{}, options[:no_prompt], true)['value']
402
+
403
+ if option_type_id
404
+ option_types << {'id' => option_type_id.to_i}
405
+ avail_type_options.reject! {|it| it['value'] == option_type_id}
406
+ else
407
+ break
408
+ end
409
+ end
410
+ end
411
+
412
+ params['optionTypes'] = option_types if option_types
413
+
414
+ # nodes
415
+ ['master', 'worker'].each do |node_type|
416
+ nodes = []
417
+ if cluster_type["has#{node_type.capitalize}s"]
418
+ if options["#{node_type}s".to_sym]
419
+ options["#{node_type}s".to_sym].each do |container_type_id|
420
+ count = 1
421
+ if container_type_id.include?('/')
422
+ parts = container_type_id.split('/')
423
+ container_type_id = parts[0]
424
+ count = parts[1].to_i if parts.count > 1
425
+ end
426
+
427
+ if @library_container_types_interface.get(nil, container_type_id.to_i).nil?
428
+ print_red_alert "Container type #{container_type_id} not found"
429
+ exit 1
430
+ else
431
+ nodes << {'nodeCount' => count, 'containerType' => {'id' => container_type_id.to_i}}
432
+ end
433
+ end
434
+ else
435
+ avail_container_types = @library_container_types_interface.list(nil, {'technology' => provision_type['code'], 'max' => 1000})['containerTypes'].collect {|it| {'name' => it['name'], 'value' => it['id']}}
436
+ while !avail_container_types.empty? && Morpheus::Cli::OptionTypes.confirm("Add #{nodes.empty? ? '' : 'another '}#{node_type} node?", {:default => false}) do
437
+ container_type_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'select', 'fieldLabel' => "#{node_type.capitalize} Node", 'selectOptions' => avail_container_types, 'required' => true}],options[:options],@api_client,{}, options[:no_prompt], true)['value']
438
+ count = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'number', 'fieldLabel' => "#{node_type.capitalize} Node Count", 'required' => true, 'defaultValue' => 1}], options[:options], @api_client, {}, options[:no_prompt])['value']
439
+ nodes << {'nodeCount' => count, 'containerType' => {'id' => container_type_id.to_i}}
440
+ avail_container_types.reject! {|it| it['value'] == container_type_id}
441
+ end
442
+ end
443
+ end
444
+ params["#{node_type}s"] = nodes
445
+ end
446
+ payload = {'layout' => params}
447
+ end
448
+
449
+ @library_cluster_layouts_interface.setopts(options)
450
+ if options[:dry_run]
451
+ print_dry_run @library_cluster_layouts_interface.dry.create(payload)
452
+ return
453
+ end
454
+
455
+ json_response = @library_cluster_layouts_interface.create(payload)
456
+
457
+ if options[:json]
458
+ print JSON.pretty_generate(json_response), "\n"
459
+ return
460
+ end
461
+ print_green_success "Added Cluster Layout #{params['name']}"
462
+ get([json_response['id']])
463
+ rescue RestClient::Exception => e
464
+ print_rest_exception(e, options)
465
+ exit 1
466
+ end
467
+ end
468
+
469
+ def update(args)
470
+ options = {}
471
+ params = {}
472
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
473
+ opts.banner = subcommand_usage("[name] [options]")
474
+ opts.on('-n', '--name VALUE', String, "Name for this cluster layout") do |val|
475
+ params['name'] = val
476
+ end
477
+ opts.on('-D', '--description VALUE', String, "Description") do |val|
478
+ params['description'] = val
479
+ end
480
+ opts.on('-v', '--version VALUE', String, "Version") do |val|
481
+ params['computeVersion'] = val
482
+ end
483
+ opts.on('-c', '--creatable [on|off]', String, "Can be used to enable / disable creatable layout. Default is on") do |val|
484
+ params['creatable'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
485
+ end
486
+ opts.on('-g', '--cluster-type CODE', String, "Cluster type. This is the cluster type code.") do |val|
487
+ options[:clusterTypeCode] = val
488
+ end
489
+ opts.on('-t', '--technology CODE', String, "Technology. This is the provision type code.") do |val|
490
+ options[:provisionTypeCode] = val
491
+ end
492
+ opts.on('-m', '--min-memory NUMBER', String, "Min memory. Assumes MB unless optional modifier specified, ex: 1GB") do |val|
493
+ bytes = parse_bytes_param(val, '--min-memory', 'MB')
494
+ params['memoryRequirement'] = bytes[:bytes]
495
+ end
496
+ opts.on('-w', '--workflow ID', String, "Workflow") do |val|
497
+ options[:taskSetId] = val.to_i
498
+ end
499
+ opts.on(nil, '--clear-workflow', "Removes workflow from cluster layout") do
500
+ params['taskSets'] = []
501
+ end
502
+ opts.on('-s', '--auto-scale [on|off]', String, "Can be used to enable / disable horizontal scaling. Default is on") do |val|
503
+ params['hasAutoScale'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
504
+ end
505
+ opts.on('--evars-json JSON', String, 'Environment variables JSON: {"name":"Foo", "value":"Bar", "masked":true, "export":true}' ) do |val|
506
+ begin
507
+ evars = JSON.parse(val.to_s)
508
+ params['environmentVariables'] = evars.kind_of?(Array) ? evars : [evars]
509
+ rescue JSON::ParserError => e
510
+ print_red_alert "Unable to parse evars JSON"
511
+ exit 1
512
+ end
513
+ end
514
+ opts.on('-e', '--evars LIST', Array, "Environment variables list. Comma delimited list of name=value pairs") do |val|
515
+ params['environmentVariables'] = val.collect do |nv|
516
+ parts = nv.split('=')
517
+ {'name' => parts[0].strip, 'value' => (parts.count > 1 ? parts[1].strip : '')}
518
+ end
519
+ end
520
+ opts.on(nil, '--clear-evars', "Removes all environment variables") do
521
+ params['environmentVariables'] = []
522
+ end
523
+ opts.on('-o', '--opt-types LIST', Array, "Option types, comma separated list of option type IDs") do |val|
524
+ options[:optionTypes] = val
525
+ end
526
+ opts.on(nil, '--clear-opt-types', "Removes all options") do
527
+ params['optionTypes'] = []
528
+ end
529
+ opts.on('--masters LIST', Array, "List of master nodes. Comma separated container types IDs in format id[/count], ex: 100,101/3") do |val|
530
+ options[:masters] = val
531
+ end
532
+ opts.on('--clear-masters', Array, "Removes all master nodes") do
533
+ params['masters'] = []
534
+ end
535
+ opts.on('--workers LIST', Array, "List of workers. Comma separated container types IDs in format id[/count], ex: 100,101/3") do |val|
536
+ options[:workers] = val
537
+ end
538
+ opts.on('--clear-workers', Array, "Removes all worker nodes") do
539
+ params['workers'] = []
540
+ end
541
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
542
+ opts.footer = "Update a cluster layout." + "\n" +
543
+ "[layout] is required. This is the name or id of a cluster layout."
544
+ end
545
+ optparse.parse!(args)
546
+ connect(options)
547
+ if args.count != 1
548
+ print_error Morpheus::Terminal.angry_prompt
549
+ puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
550
+ return 1
551
+ end
552
+
553
+ begin
554
+ layout = find_layout_by_name_or_id(args[0])
555
+ if layout.nil?
556
+ return 1
557
+ end
558
+
559
+ if options[:payload]
560
+ payload = options[:payload]
561
+ else
562
+ # support the old -O OPTION switch
563
+ params.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
564
+
565
+ # cluster type
566
+ cluster_type = nil
567
+ if options[:clusterTypeCode]
568
+ cluster_type = find_cluster_type_by_code(options[:clusterTypeCode])
569
+ if cluster_type.nil?
570
+ print_red_alert "Cluster type #{options[:clusterTypeCode]} not found"
571
+ exit 1
572
+ end
573
+ params['groupType'] = {'id' => cluster_type['id']}
574
+ end
575
+
576
+ # technology customSupported, createServer
577
+ if options[:provisionTypeCode]
578
+ provision_type = find_provision_type_by_code(options[:provisionTypeCode])
579
+ if provision_type.nil?
580
+ print_red_alert "Technology #{options[:provisionTypeCode]} not found"
581
+ exit 1
582
+ end
583
+ params['provisionType'] = {'id' => provision_type['id']}
584
+ end
585
+
586
+ # workflow
587
+ if options[:taskSetId]
588
+ task_set = @task_sets_interface.get(options[:taskSetId])['taskSet']
589
+
590
+ if !task_set
591
+ print_red_alert "Workflow #{options[:taskSetId]} not found"
592
+ exit 1
593
+ end
594
+ params['taskSets'] = [{'id' => task_set['id']}]
595
+ end
596
+
597
+ # option types
598
+ if options[:optionTypes]
599
+ option_types = []
600
+ options[:optionTypes].each do |option_type_id|
601
+ if @options_types_interface.get(option_type_id.to_i).nil?
602
+ print_red_alert "Option type #{option_type_id} not found"
603
+ exit 1
604
+ else
605
+ option_types << {'id' => option_type_id.to_i}
606
+ end
607
+ end
608
+ params['optionTypes'] = option_types if option_types
609
+ end
610
+
611
+ # nodes
612
+ ['master', 'worker'].each do |node_type|
613
+ nodes = []
614
+ if options["#{node_type}s".to_sym]
615
+ cluster_type ||= find_cluster_type_by_code(layout['groupType']['code'])
616
+
617
+ if !cluster_type["has#{node_type.capitalize}s"]
618
+ print_red_alert "#{node_type.capitalize}s not support for a #{cluster_type['name']}"
619
+ exit 1
620
+ else
621
+ options["#{node_type}s".to_sym].each do |container_type_id|
622
+ count = 1
623
+ if container_type_id.include?('/')
624
+ parts = container_type_id.split('/')
625
+ container_type_id = parts[0]
626
+ count = parts[1].to_i if parts.count > 1
627
+ end
628
+
629
+ if @library_container_types_interface.get(nil, container_type_id.to_i).nil?
630
+ print_red_alert "Container type #{container_type_id} not found"
631
+ exit 1
632
+ else
633
+ nodes << {'nodeCount' => count, 'containerType' => {'id' => container_type_id.to_i}}
634
+ end
635
+ end
636
+ end
637
+ params["#{node_type}s"] = nodes
638
+ end
639
+ end
640
+
641
+ if params.empty?
642
+ print_green_success "Nothing to update"
643
+ exit 1
644
+ end
645
+ payload = {'layout' => params}
646
+ end
647
+
648
+ @library_cluster_layouts_interface.setopts(options)
649
+ if options[:dry_run]
650
+ print_dry_run @library_cluster_layouts_interface.dry.update(layout['id'], payload)
651
+ return
652
+ end
653
+
654
+ json_response = @library_cluster_layouts_interface.update(layout['id'], payload)
655
+
656
+ if options[:json]
657
+ print JSON.pretty_generate(json_response), "\n"
658
+ return
659
+ elsif !options[:quiet]
660
+ if json_response['success']
661
+ print_green_success "Updated cluster Layout #{params['name']}"
662
+ get([layout['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
663
+ else
664
+ print_red_alert "Error updating cluster layout: #{json_response['msg'] || json_response['errors']}"
665
+ end
666
+ end
667
+ rescue RestClient::Exception => e
668
+ print_rest_exception(e, options)
669
+ exit 1
670
+ end
671
+ end
672
+
673
+ def clone(args)
674
+ options = {}
675
+ params = {}
676
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
677
+ opts.banner = subcommand_usage("[layout]")
678
+ opts.on('-n', '--name VALUE', String, "Name for new cluster layout. Defaults to 'Copy of...'") do |val|
679
+ params['name'] = val
680
+ end
681
+ opts.on('-D', '--description VALUE', String, "Description") do |val|
682
+ params['description'] = val
683
+ end
684
+ opts.on('-v', '--version VALUE', String, "Version") do |val|
685
+ params['computeVersion'] = val
686
+ end
687
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
688
+ opts.footer = "Clone a cluster layout." + "\n" +
689
+ "[layout] is required. This is the name or id of a cluster layout being cloned."
690
+ end
691
+ optparse.parse!(args)
692
+
693
+ if args.count != 1
694
+ print_error Morpheus::Terminal.angry_prompt
695
+ puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
696
+ return 1
697
+ end
698
+
699
+ connect(options)
700
+
701
+ begin
702
+ layout = find_layout_by_name_or_id(args[0])
703
+ if layout.nil?
704
+ return 1
705
+ end
706
+
707
+ if options[:payload]
708
+ params = options[:payload]
709
+ else
710
+ # support the old -O OPTION switch
711
+ params.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
712
+ end
713
+
714
+ @library_cluster_layouts_interface.setopts(options)
715
+ if options[:dry_run]
716
+ print_dry_run @library_cluster_layouts_interface.dry.clone(layout['id'], params)
717
+ return
718
+ end
719
+
720
+ json_response = @library_cluster_layouts_interface.clone(layout['id'], params)
721
+
722
+ if options[:json]
723
+ print JSON.pretty_generate(json_response), "\n"
724
+ return
725
+ end
726
+ print_green_success "Added Cluster Layout #{params['name']}"
727
+ get([json_response['id']])
728
+ rescue RestClient::Exception => e
729
+ print_rest_exception(e, options)
730
+ exit 1
731
+ end
732
+ end
733
+
734
+ def remove(args)
735
+ options = {}
736
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
737
+ opts.banner = subcommand_usage("[layout]")
738
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
739
+ opts.footer = "Delete a cluster layout." + "\n" +
740
+ "[layout] is required. This is the name or id of a cluster layout."
741
+ end
742
+ optparse.parse!(args)
743
+
744
+ if args.count != 1
745
+ print_error Morpheus::Terminal.angry_prompt
746
+ puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
747
+ return 1
748
+ end
749
+
750
+ connect(options)
751
+
752
+ begin
753
+ layout = find_layout_by_name_or_id(args[0])
754
+ if layout.nil?
755
+ return 1
756
+ end
757
+
758
+ unless Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the cluster layout #{layout['name']}?", options)
759
+ exit
760
+ end
761
+
762
+ @library_cluster_layouts_interface.setopts(options)
763
+ if options[:dry_run]
764
+ print_dry_run @library_cluster_layouts_interface.dry.destroy(layout['id'])
765
+ return
766
+ end
767
+ json_response = @library_cluster_layouts_interface.destroy(layout['id'])
768
+
769
+ if options[:json]
770
+ print JSON.pretty_generate(json_response)
771
+ print "\n"
772
+ elsif !options[:quiet]
773
+ if json_response['success']
774
+ print_green_success "Removed Cluster Layout #{layout['name']}"
775
+ else
776
+ print_red_alert "Error removing cluster layout: #{json_response['msg'] || json_response['errors']}"
777
+ end
778
+ end
779
+ return 0
780
+ rescue RestClient::Exception => e
781
+ print_rest_exception(e, options)
782
+ exit 1
783
+ end
784
+ end
785
+
786
+ private
787
+
788
+ def find_layout_by_name_or_id(val)
789
+ if val.to_s =~ /\A\d{1,}\Z/
790
+ return find_layout_by_id(val)
791
+ else
792
+ return find_layout_by_name(val)
793
+ end
794
+ end
795
+
796
+ def find_layout_by_id(id)
797
+ begin
798
+ json_response = @library_cluster_layouts_interface.get(id.to_i)
799
+ return json_response['layout']
800
+ rescue RestClient::Exception => e
801
+ if e.response && e.response.code == 404
802
+ print_red_alert "Cluster layout not found by id #{id}"
803
+ else
804
+ raise e
805
+ end
806
+ end
807
+ end
808
+
809
+ def find_layout_by_name(name)
810
+ layouts = @library_cluster_layouts_interface.list(instance_type_id, {name: name.to_s})['layouts']
811
+ if layouts.empty?
812
+ print_red_alert "Cluster layout not found by name #{name}"
813
+ return nil
814
+ elsif layouts.size > 1
815
+ print_red_alert "#{layouts.size} cluster layouts found by name #{name}"
816
+ print_layouts_table(layouts, {color: red})
817
+ print_red_alert "Try using ID instead"
818
+ print reset,"\n"
819
+ return nil
820
+ else
821
+ return layouts[0]
822
+ end
823
+ end
824
+
825
+ def cluster_types
826
+ @cluster_types ||= @clusters_interface.cluster_types['clusterTypes']
827
+ end
828
+
829
+ def find_cluster_type_by_code(code)
830
+ cluster_types.find {|ct| ct['code'] == code}
831
+ end
832
+
833
+ def provision_types
834
+ @provision_types ||= @provision_types_interface.list({customSupported: true, createServer: true})['provisionTypes']
835
+ end
836
+
837
+ def find_provision_type_by_code(code)
838
+ provision_types.find {|it| it['code'] == code}
839
+ end
840
+
841
+ def printable_byte_size(val)
842
+ val = val.to_i / 1024 / 1024
843
+ label = 'MB'
844
+
845
+ if val > 1024
846
+ val = val / 1024
847
+ label = 'GB'
848
+ end
849
+ "#{val} #{label}"
850
+ end
851
+
852
+ def layout_cloud_type(layout)
853
+ layout['provisionType'] ? layout['provisionType']['name'] : (layout['groupType'] ? layout['groupType']['name'] : 'Standard')
854
+ end
855
+
856
+ def prompt_evar(options)
857
+ name = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'text', 'fieldLabel' => 'Variable Name', 'required' => true}], options[:options], @api_client,{})['value']
858
+ value = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'text', 'fieldLabel' => 'Variable Value', 'required' => false}], options[:options], @api_client,{})['value']
859
+ masked = Morpheus::Cli::OptionTypes.confirm("Variable Masked?", {:default => false}) == true
860
+ export = Morpheus::Cli::OptionTypes.confirm("Variable Label?", {:default => false}) == true
861
+ {'name' => name, 'value' => value, 'masked' => masked, 'export' => export}
862
+ end
863
+ end