morpheus-cli 4.1.9 → 4.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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