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 +4 -4
- data/lib/morpheus/api/api_client.rb +5 -2
- data/lib/morpheus/api/apps_interface.rb +7 -0
- data/lib/morpheus/api/instances_interface.rb +7 -0
- data/lib/morpheus/api/library_cluster_layouts_interface.rb +53 -0
- data/lib/morpheus/api/library_resource_specs_interface.rb +49 -0
- data/lib/morpheus/cli.rb +2 -0
- data/lib/morpheus/cli/apps.rb +42 -1
- data/lib/morpheus/cli/cli_command.rb +18 -0
- data/lib/morpheus/cli/clusters.rb +4 -4
- data/lib/morpheus/cli/instances.rb +41 -2
- data/lib/morpheus/cli/library_cluster_layouts_command.rb +863 -0
- data/lib/morpheus/cli/library_resource_specs_command.rb +418 -0
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +29 -6
- data/lib/morpheus/cli/service_plans_command.rb +1 -19
- data/lib/morpheus/cli/version.rb +1 -1
- metadata +7 -5
- data/lib/morpheus/api/library_compute_type_layouts_interface.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '090c9aa2420e68bbded20a8277e475990ce50a2b45adc63cb93d9d3f2fb3cf79'
|
4
|
+
data.tar.gz: e60d21d206cdf6de9a8658597e3f2bfa58cb193693306029df243755384247b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
603
|
-
Morpheus::
|
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
|
data/lib/morpheus/cli.rb
CHANGED
@@ -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'
|
data/lib/morpheus/cli/apps.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
@
|
3608
|
+
@cluster_layouts_interface.get(id)['layout'] rescue nil
|
3609
3609
|
end
|
3610
3610
|
|
3611
3611
|
def find_layout_by_name(name)
|
3612
|
-
@
|
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
|
-
@
|
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
|