morpheus-cli 8.1.2 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +2 -2
- data/lib/morpheus/api/api_client.rb +8 -0
- data/lib/morpheus/api/clusters_interface.rb +24 -0
- data/lib/morpheus/api/groups_interface.rb +2 -1
- data/lib/morpheus/api/instances_interface.rb +1 -1
- data/lib/morpheus/api/options_interface.rb +1 -1
- data/lib/morpheus/api/servers_interface.rb +0 -1
- data/lib/morpheus/api/storage_volumes_interface.rb +8 -0
- data/lib/morpheus/api/support_bundles_interface.rb +46 -0
- data/lib/morpheus/api/systems_interface.rb +32 -0
- data/lib/morpheus/api/tokens_interface.rb +39 -0
- data/lib/morpheus/api/virtual_images_interface.rb +10 -2
- data/lib/morpheus/cli/cli_command.rb +6 -1
- data/lib/morpheus/cli/commands/backups_command.rb +9 -6
- data/lib/morpheus/cli/commands/clients_command.rb +60 -74
- data/lib/morpheus/cli/commands/clouds.rb +30 -2
- data/lib/morpheus/cli/commands/clusters.rb +77 -0
- data/lib/morpheus/cli/commands/execution_request_command.rb +6 -2
- data/lib/morpheus/cli/commands/groups.rb +46 -23
- data/lib/morpheus/cli/commands/hosts.rb +21 -14
- data/lib/morpheus/cli/commands/instances.rb +32 -6
- data/lib/morpheus/cli/commands/storage_volumes.rb +78 -3
- data/lib/morpheus/cli/commands/support_bundles_command.rb +606 -0
- data/lib/morpheus/cli/commands/systems.rb +606 -2
- data/lib/morpheus/cli/commands/tokens_command.rb +391 -0
- data/lib/morpheus/cli/commands/workflows.rb +16 -3
- data/lib/morpheus/cli/mixins/infrastructure_helper.rb +30 -14
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +21 -7
- data/lib/morpheus/cli/version.rb +1 -1
- data/test/api/systems_interface_test.rb +26 -0
- data/test/cli/storage_volumes_test.rb +79 -0
- data/test/cli/systems_test.rb +206 -0
- metadata +12 -2
|
@@ -13,6 +13,7 @@ class Morpheus::Cli::Clusters
|
|
|
13
13
|
register_subcommands :list_workers, :add_worker, :remove_worker, :update_worker_count
|
|
14
14
|
register_subcommands :list_masters
|
|
15
15
|
register_subcommands :upgrade_cluster
|
|
16
|
+
register_subcommands :update_version
|
|
16
17
|
register_subcommands :list_volumes, :remove_volume
|
|
17
18
|
register_subcommands :list_namespaces, :get_namespace, :add_namespace, :update_namespace, :remove_namespace
|
|
18
19
|
register_subcommands :list_containers, :remove_container, :restart_container, :get_container
|
|
@@ -697,6 +698,13 @@ class Morpheus::Cli::Clusters
|
|
|
697
698
|
ssh_host_option = option_type_list.select{|it| it['fieldName'] == 'sshHosts'}.first
|
|
698
699
|
ssh_host_option['minCount'] = server_count unless ssh_host_option.nil?
|
|
699
700
|
|
|
701
|
+
# Fix for bug in 9.0.0 - 9.0.2 with optionType data where defaultValue is set to "unmanaged" for witness.id, which is not a valid value. Remove it so the user can select a valid value.
|
|
702
|
+
option_type_list.each do |option_type|
|
|
703
|
+
if option_type['fieldName'] == 'witness.id' && option_type['defaultValue'] == 'unmanaged'
|
|
704
|
+
option_type.delete('defaultValue')
|
|
705
|
+
end
|
|
706
|
+
end
|
|
707
|
+
|
|
700
708
|
# Server options
|
|
701
709
|
server_payload.deep_merge!(Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options].deep_merge({:context_map => {'domain' => ''}}), @api_client, api_params, options[:no_prompt], true))
|
|
702
710
|
|
|
@@ -3064,6 +3072,7 @@ class Morpheus::Cli::Clusters
|
|
|
3064
3072
|
"Active" => lambda { |it| format_boolean(it['active']) },
|
|
3065
3073
|
"Visibility" => lambda { |it| it['visibility'].nil? ? '' : it['visibility'].to_s.capitalize },
|
|
3066
3074
|
"Tenants" => lambda { |it| it['tenants'].nil? ? '' : it['tenants'].collect {|it| it['name']}.join(', ') },
|
|
3075
|
+
"Supports VM Secure Metadata" => lambda { |it| format_boolean(it['supportsVmSecureMetadata']) },
|
|
3067
3076
|
"Cluster" => lambda { |it| cluster['name'] }
|
|
3068
3077
|
}
|
|
3069
3078
|
print_description_list(description_cols, datastore)
|
|
@@ -3233,6 +3242,12 @@ class Morpheus::Cli::Clusters
|
|
|
3233
3242
|
opts.on('--active [on|off]', String, "Enable datastore") do |val|
|
|
3234
3243
|
options[:active] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
|
|
3235
3244
|
end
|
|
3245
|
+
opts.on('--supports-vm-secure-metadata [on|off]', String, "Enable VM Secure Metadata support") do |val|
|
|
3246
|
+
options[:supportsVmSecureMetadata] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
|
|
3247
|
+
end
|
|
3248
|
+
opts.on('--heartbeat-target [on|off]', String, "Set as the heartbeat target. Default is on") do |val|
|
|
3249
|
+
options[:heartbeatTarget] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
|
|
3250
|
+
end
|
|
3236
3251
|
add_perms_options(opts, options, ['plans', 'groupDefaults'])
|
|
3237
3252
|
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
|
3238
3253
|
opts.footer = "Update a cluster datastore.\n" +
|
|
@@ -3250,6 +3265,7 @@ class Morpheus::Cli::Clusters
|
|
|
3250
3265
|
cluster = find_cluster_by_name_or_id(args[0])
|
|
3251
3266
|
return 1 if cluster.nil?
|
|
3252
3267
|
datastore = find_datastore_by_name_or_id(cluster['id'], args[1])
|
|
3268
|
+
datastore_type = find_datastore_type_by_code(datastore['datastoreType']['code']) rescue nil
|
|
3253
3269
|
if datastore.nil?
|
|
3254
3270
|
print_red_alert "Datastore not found by '#{args[1]}'"
|
|
3255
3271
|
exit 1
|
|
@@ -3264,6 +3280,15 @@ class Morpheus::Cli::Clusters
|
|
|
3264
3280
|
else
|
|
3265
3281
|
payload = {'datastore' => {}}
|
|
3266
3282
|
payload['datastore']['active'] = options[:active].nil? ? (Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'description' => 'Datastore Active', 'defaultValue' => true}], options[:options], @api_client))['active'] == 'on' : options[:active]
|
|
3283
|
+
payload['datastore']['supportsVmSecureMetadata'] = options[:supportsVmSecureMetadata] unless options[:supportsVmSecureMetadata].nil?
|
|
3284
|
+
|
|
3285
|
+
if !options[:heartbeatTarget].nil?
|
|
3286
|
+
payload['datastore']['heartbeatTarget'] = options[:heartbeatTarget]
|
|
3287
|
+
elsif datastore_type && datastore_type['heartbeatTargetCapable'] == true
|
|
3288
|
+
val = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'heartbeatTarget', 'fieldLabel' => 'Heartbeat Target', 'type' => 'checkbox', 'description' => 'Heartbeat Target', 'defaultValue' => datastore['heartbeatTarget']}], options[:options], @api_client)['heartbeatTarget']
|
|
3289
|
+
payload['datastore']['heartbeatTarget'] = val == 'on' || val == true unless val.nil? || val == ''
|
|
3290
|
+
end
|
|
3291
|
+
|
|
3267
3292
|
|
|
3268
3293
|
perms = prompt_permissions(options.merge({:available_plans => namespace_service_plans}), datastore['owner']['id'] == current_user['accountId'] ? ['plans', 'groupDefaults'] : ['plans', 'groupDefaults', 'visibility', 'tenants'])
|
|
3269
3294
|
perms_payload = {}
|
|
@@ -4421,6 +4446,58 @@ class Morpheus::Cli::Clusters
|
|
|
4421
4446
|
Morpheus::Cli::ClusterTypes.new.get(args)
|
|
4422
4447
|
end
|
|
4423
4448
|
|
|
4449
|
+
def update_version(args)
|
|
4450
|
+
params = {}
|
|
4451
|
+
options = {}
|
|
4452
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
4453
|
+
opts.banner = subcommand_usage("[cluster]")
|
|
4454
|
+
build_standard_update_options(opts, options, [:auto_confirm])
|
|
4455
|
+
opts.footer = "Updates HVM cluster layout to a new version.\n" +
|
|
4456
|
+
"[cluster] is required. This is the name or id of an existing cluster.\n"
|
|
4457
|
+
end
|
|
4458
|
+
optparse.parse!(args)
|
|
4459
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
4460
|
+
connect(options)
|
|
4461
|
+
|
|
4462
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
|
4463
|
+
return 1 if cluster.nil?
|
|
4464
|
+
|
|
4465
|
+
payload = {}
|
|
4466
|
+
if options[:payload]
|
|
4467
|
+
payload = options[:payload]
|
|
4468
|
+
payload.deep_merge!(parse_passed_options(options))
|
|
4469
|
+
else
|
|
4470
|
+
payload.deep_merge!(parse_passed_options(options))
|
|
4471
|
+
|
|
4472
|
+
available_updates = @clusters_interface.available_updates(cluster['id'])['updateDefinitions']
|
|
4473
|
+
if available_updates.empty?
|
|
4474
|
+
print yellow,"No updates available for cluster #{cluster['name']}",reset,"\n"
|
|
4475
|
+
return 1, "No updates available for cluster #{cluster['name']}"
|
|
4476
|
+
end
|
|
4477
|
+
version_options = available_updates.collect { |it| {'name' => it['name'], 'value' => it['id']} }
|
|
4478
|
+
update_definition_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'updateDefinitionId', 'type' => 'select', 'fieldLabel' => 'Update', 'selectOptions' => version_options, 'required' => true, 'description' => 'Select version update to execute' }],options[:options],api_client,{})['updateDefinitionId']
|
|
4479
|
+
payload.deep_merge!({'updateDefinitionId' => update_definition_id})
|
|
4480
|
+
end
|
|
4481
|
+
target_version = available_updates.find { |it| it['id'] == payload['updateDefinitionId'] }['updateVersion'] rescue nil
|
|
4482
|
+
if target_version.nil?
|
|
4483
|
+
print_red_alert "Unable to determine target version for update definition #{payload['updateDefinitionId']}"
|
|
4484
|
+
return 1, "Unable to determine target version for update definition #{payload['updateDefinitionId']}"
|
|
4485
|
+
end
|
|
4486
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to update cluster #{cluster['name']} to version #{target_version}?")
|
|
4487
|
+
return 9, "aborted command"
|
|
4488
|
+
end
|
|
4489
|
+
@clusters_interface.setopts(options)
|
|
4490
|
+
if options[:dry_run]
|
|
4491
|
+
print_dry_run @clusters_interface.dry.execute_update(cluster['id'], payload)
|
|
4492
|
+
return
|
|
4493
|
+
end
|
|
4494
|
+
json_response = @clusters_interface.execute_update(cluster['id'], payload)
|
|
4495
|
+
render_response(json_response, options) do
|
|
4496
|
+
print_green_success "Cluster #{cluster['name']} is being updated to version #{target_version}..."
|
|
4497
|
+
end
|
|
4498
|
+
return 0, nil
|
|
4499
|
+
end
|
|
4500
|
+
|
|
4424
4501
|
private
|
|
4425
4502
|
|
|
4426
4503
|
def print_clusters_table(clusters, opts={})
|
|
@@ -83,13 +83,13 @@ class Morpheus::Cli::ExecutionRequestCommand
|
|
|
83
83
|
if options[:refresh_interval].nil? || options[:refresh_interval].to_f < 0
|
|
84
84
|
options[:refresh_interval] = default_refresh_interval
|
|
85
85
|
end
|
|
86
|
-
if
|
|
86
|
+
if is_finished(execution_request)
|
|
87
87
|
# it is finished
|
|
88
88
|
else
|
|
89
89
|
print cyan
|
|
90
90
|
refresh_display_seconds = options[:refresh_interval] % 1.0 == 0 ? options[:refresh_interval].to_i : options[:refresh_interval]
|
|
91
91
|
print "Execution request has not yet finished. Refreshing every #{refresh_display_seconds} seconds"
|
|
92
|
-
while execution_request
|
|
92
|
+
while !is_finished(execution_request) do
|
|
93
93
|
sleep(options[:refresh_interval])
|
|
94
94
|
print cyan,".",reset
|
|
95
95
|
json_response = @execution_request_interface.get(execution_request_id, params)
|
|
@@ -326,4 +326,8 @@ class Morpheus::Cli::ExecutionRequestCommand
|
|
|
326
326
|
out
|
|
327
327
|
end
|
|
328
328
|
|
|
329
|
+
def is_finished(execution_request)
|
|
330
|
+
['executing', 'pending', 'new'].include?(execution_request['status']) ? false : true
|
|
331
|
+
end
|
|
332
|
+
|
|
329
333
|
end
|
|
@@ -2,6 +2,7 @@ require 'morpheus/cli/cli_command'
|
|
|
2
2
|
|
|
3
3
|
class Morpheus::Cli::Groups
|
|
4
4
|
include Morpheus::Cli::CliCommand
|
|
5
|
+
include Morpheus::Cli::AccountsHelper
|
|
5
6
|
include Morpheus::Cli::InfrastructureHelper
|
|
6
7
|
include Morpheus::Cli::OptionSourceHelper
|
|
7
8
|
|
|
@@ -19,6 +20,8 @@ class Morpheus::Cli::Groups
|
|
|
19
20
|
@groups_interface = @api_client.groups
|
|
20
21
|
@clouds_interface = @api_client.clouds
|
|
21
22
|
@options_interface = @api_client.options
|
|
23
|
+
@accounts_interface = @api_client.accounts
|
|
24
|
+
@account_users_interface = @api_client.account_users
|
|
22
25
|
@active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
|
|
23
26
|
end
|
|
24
27
|
|
|
@@ -37,12 +40,31 @@ class Morpheus::Cli::Groups
|
|
|
37
40
|
opts.on('--all-labels LABEL', String, "Filter by labels, must match all of the values") do |val|
|
|
38
41
|
add_query_parameter(params, 'allLabels', parse_labels(val))
|
|
39
42
|
end
|
|
43
|
+
opts.on('--include-tenants','--include-tenants', "Include sub tenant groups") do
|
|
44
|
+
options[:include_tenants] = true
|
|
45
|
+
params['includeTenants'] = true
|
|
46
|
+
end
|
|
47
|
+
opts.on('--tenant TENANT', String, "Tenant Name or ID" ) do |val|
|
|
48
|
+
options[:tenant] = val
|
|
49
|
+
end
|
|
40
50
|
build_standard_list_options(opts, options)
|
|
41
51
|
opts.footer = "List groups."
|
|
42
52
|
end
|
|
43
53
|
optparse.parse!(args)
|
|
44
54
|
verify_args!(args:args, optparse:optparse, count:0)
|
|
45
55
|
connect(options)
|
|
56
|
+
if options[:tenant]
|
|
57
|
+
if options[:tenant].to_s !~ /\A\d{1,}\Z/
|
|
58
|
+
account = find_account_by_name_or_id(options[:tenant])
|
|
59
|
+
if account.nil?
|
|
60
|
+
return 1, "Tenant not found by name: #{options[:tenant]}"
|
|
61
|
+
else
|
|
62
|
+
params['tenantId'] = account['id']
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
params['tenantId'] = options[:tenant]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
46
68
|
params.merge!(parse_list_options(options))
|
|
47
69
|
@groups_interface.setopts(options)
|
|
48
70
|
if options[:dry_run]
|
|
@@ -71,6 +93,9 @@ class Morpheus::Cli::Groups
|
|
|
71
93
|
options = {}
|
|
72
94
|
optparse = Morpheus::Cli::OptionParser.new do|opts|
|
|
73
95
|
opts.banner = subcommand_usage("[name]")
|
|
96
|
+
opts.on('--include-tenants','--include-tenants', "Include sub tenant groups when finding group by name") do
|
|
97
|
+
options[:include_tenants] = true
|
|
98
|
+
end
|
|
74
99
|
build_standard_get_options(opts, options)
|
|
75
100
|
opts.footer = <<-EOT
|
|
76
101
|
Get details about a group.
|
|
@@ -86,25 +111,20 @@ EOT
|
|
|
86
111
|
end
|
|
87
112
|
end
|
|
88
113
|
|
|
89
|
-
def _get(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
json_response = {'group' => group}
|
|
104
|
-
|
|
105
|
-
render_result = render_with_format(json_response, options)
|
|
106
|
-
return 0 if render_result
|
|
107
|
-
|
|
114
|
+
def _get(id, options={})
|
|
115
|
+
params = {}
|
|
116
|
+
group = nil
|
|
117
|
+
if id.to_s !~ /\A\d{1,}\Z/
|
|
118
|
+
group = find_group_by_name_or_id(id, options[:include_tenants])
|
|
119
|
+
id = group['id']
|
|
120
|
+
end
|
|
121
|
+
@groups_interface.setopts(options)
|
|
122
|
+
if options[:dry_run]
|
|
123
|
+
print_dry_run @groups_interface.dry.get(id.to_i, params)
|
|
124
|
+
return
|
|
125
|
+
end
|
|
126
|
+
json_response = @groups_interface.get(id.to_i, params)
|
|
127
|
+
render_response(json_response, options, 'group') do
|
|
108
128
|
group = json_response['group']
|
|
109
129
|
group_stats = group['stats']
|
|
110
130
|
# serverCounts moved to zone.stats.serverCounts
|
|
@@ -124,6 +144,7 @@ EOT
|
|
|
124
144
|
"Location" => 'location',
|
|
125
145
|
"Labels" => lambda {|it| format_list(it['labels'], '') rescue '' },
|
|
126
146
|
"Clouds" => lambda {|it| it['zones'].collect {|z| z['name'] }.join(', ') },
|
|
147
|
+
"Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
|
127
148
|
#"Instances" => lambda {|it| it['stats']['instanceCounts']['all'] rescue '' },
|
|
128
149
|
# "Hosts" => lambda {|it| it['stats']['serverCounts']['host'] rescue it['serverCount'] },
|
|
129
150
|
# "VMs" => lambda {|it| it['stats']['serverCounts']['vm'] rescue '' },
|
|
@@ -665,13 +686,14 @@ EOT
|
|
|
665
686
|
|
|
666
687
|
protected
|
|
667
688
|
|
|
668
|
-
def print_groups_table(groups,
|
|
669
|
-
table_color =
|
|
689
|
+
def print_groups_table(groups, options={})
|
|
690
|
+
table_color = options[:color] || cyan
|
|
670
691
|
active_group = @active_group_id ? groups.find {|group| group['id'] == @active_group_id } : nil
|
|
671
692
|
rows = groups.collect do |group|
|
|
672
693
|
{
|
|
673
694
|
id: active_group ? (((@active_group_id && (@active_group_id == group['id'])) ? "=> " : " ") + group['id'].to_s) : group['id'],
|
|
674
695
|
name: group['name'],
|
|
696
|
+
tenant: group['account'] ? group['account']['name'] : '',
|
|
675
697
|
labels: group['labels'],
|
|
676
698
|
location: group['location'],
|
|
677
699
|
cloud_count: group['zones'] ? group['zones'].size : 0,
|
|
@@ -685,6 +707,7 @@ EOT
|
|
|
685
707
|
#{:active => {:display_name => ""}},
|
|
686
708
|
{:id => {:display_name => (active_group ? " ID" : "ID")}},
|
|
687
709
|
{:name => {:width => 64}},
|
|
710
|
+
options[:include_tenants] || options[:tenant] ? {:tenant => {:width => 32}} : nil,
|
|
688
711
|
{:location => {:width => 32}},
|
|
689
712
|
{:labels => {:display_method => lambda {|it| format_list(it[:labels], '', 3) rescue '' }}},
|
|
690
713
|
{:cloud_count => {:display_name => "CLOUDS"}},
|
|
@@ -692,8 +715,8 @@ EOT
|
|
|
692
715
|
{:host_count => {:display_name => "HOSTS"}},
|
|
693
716
|
{:vm_count => {:display_name => "VMS"}},
|
|
694
717
|
{:baremetal_count => {:display_name => "BARE METAL"}},
|
|
695
|
-
|
|
696
|
-
print as_pretty_table(rows, columns,
|
|
718
|
+
].compact
|
|
719
|
+
print as_pretty_table(rows, columns, options)
|
|
697
720
|
end
|
|
698
721
|
|
|
699
722
|
def add_group_option_types()
|
|
@@ -113,8 +113,12 @@ class Morpheus::Cli::Hosts
|
|
|
113
113
|
opts.on( '--created-by USER', "Created By User Username or ID" ) do |val|
|
|
114
114
|
options[:created_by] = val
|
|
115
115
|
end
|
|
116
|
+
opts.on('--include-tenants','--include-tenants', "Include sub tenant servers. The default is true for this endpoint.") do
|
|
117
|
+
options[:include_tenants] = true
|
|
118
|
+
params['includeTenants'] = true
|
|
119
|
+
end
|
|
116
120
|
opts.on( '--tenant TENANT', "Tenant Name or ID" ) do |val|
|
|
117
|
-
options[:
|
|
121
|
+
options[:tenant] = val
|
|
118
122
|
end
|
|
119
123
|
opts.on('-l', '--labels LABEL', String, "Filter by labels, can match any of the values") do |val|
|
|
120
124
|
add_query_parameter(params, 'labels', parse_labels(val))
|
|
@@ -154,8 +158,8 @@ class Morpheus::Cli::Hosts
|
|
|
154
158
|
|
|
155
159
|
params.merge!(parse_list_options(options))
|
|
156
160
|
account = nil
|
|
157
|
-
if options[:
|
|
158
|
-
account = find_account_by_name_or_id(options[:
|
|
161
|
+
if options[:tenant]
|
|
162
|
+
account = find_account_by_name_or_id(options[:tenant])
|
|
159
163
|
if account.nil?
|
|
160
164
|
return 1
|
|
161
165
|
else
|
|
@@ -315,9 +319,9 @@ class Morpheus::Cli::Hosts
|
|
|
315
319
|
"External Name" => :external_name,
|
|
316
320
|
"Hostname" => :hostname,
|
|
317
321
|
"Type" => :type,
|
|
322
|
+
"Cloud" => :cloud,
|
|
318
323
|
"Owner" => :owner,
|
|
319
324
|
"Tenant" => :tenant,
|
|
320
|
-
"Cloud" => :cloud,
|
|
321
325
|
"Plan" => :plan,
|
|
322
326
|
"IP" => :ip,
|
|
323
327
|
"Private IP" => :internal_ip,
|
|
@@ -335,8 +339,6 @@ class Morpheus::Cli::Hosts
|
|
|
335
339
|
columns.delete("Hostname")
|
|
336
340
|
columns.delete("Plan")
|
|
337
341
|
columns.delete("Private IP")
|
|
338
|
-
columns.delete("Owner")
|
|
339
|
-
columns.delete("Tenant")
|
|
340
342
|
columns.delete("Power")
|
|
341
343
|
columns.delete("Created")
|
|
342
344
|
columns.delete("Updated")
|
|
@@ -369,7 +371,7 @@ class Morpheus::Cli::Hosts
|
|
|
369
371
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
370
372
|
opts.banner = subcommand_usage("[options]")
|
|
371
373
|
opts.on( '--tenant TENANT', "Tenant Name or ID" ) do |val|
|
|
372
|
-
options[:
|
|
374
|
+
options[:tenant] = val
|
|
373
375
|
end
|
|
374
376
|
opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
|
|
375
377
|
options[:group] = val
|
|
@@ -434,8 +436,8 @@ class Morpheus::Cli::Hosts
|
|
|
434
436
|
begin
|
|
435
437
|
params.merge!(parse_list_options(options))
|
|
436
438
|
account = nil
|
|
437
|
-
if options[:
|
|
438
|
-
account = find_account_by_name_or_id(options[:
|
|
439
|
+
if options[:tenant]
|
|
440
|
+
account = find_account_by_name_or_id(options[:tenant])
|
|
439
441
|
if account.nil?
|
|
440
442
|
return 1
|
|
441
443
|
else
|
|
@@ -512,6 +514,9 @@ class Morpheus::Cli::Hosts
|
|
|
512
514
|
opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
|
|
513
515
|
options[:refresh_until_status] = val.to_s.downcase
|
|
514
516
|
end
|
|
517
|
+
opts.on('--include-tenants','--include-tenants', "Include sub tenant servers when finding server by name") do
|
|
518
|
+
options[:include_tenants] = true
|
|
519
|
+
end
|
|
515
520
|
build_standard_get_options(opts, options)
|
|
516
521
|
end
|
|
517
522
|
optparse.parse!(args)
|
|
@@ -540,7 +545,7 @@ class Morpheus::Cli::Hosts
|
|
|
540
545
|
if arg.to_s =~ /\A\d{1,}\Z/
|
|
541
546
|
json_response = @servers_interface.get(arg.to_i)
|
|
542
547
|
else
|
|
543
|
-
server = find_host_by_name_or_id(arg)
|
|
548
|
+
server = find_host_by_name_or_id(arg, options[:include_tenants])
|
|
544
549
|
json_response = @servers_interface.get(server['id'])
|
|
545
550
|
# json_response = {"server" => server} need stats
|
|
546
551
|
end
|
|
@@ -2745,8 +2750,10 @@ EOT
|
|
|
2745
2750
|
end
|
|
2746
2751
|
end
|
|
2747
2752
|
|
|
2748
|
-
def find_host_by_name(name)
|
|
2749
|
-
|
|
2753
|
+
def find_host_by_name(name, include_tenants=false)
|
|
2754
|
+
params = {name: name.to_s}
|
|
2755
|
+
params['includeTenants'] = true if include_tenants
|
|
2756
|
+
results = @servers_interface.list(params)
|
|
2750
2757
|
if results['servers'].empty?
|
|
2751
2758
|
print_red_alert "Server not found by name #{name}"
|
|
2752
2759
|
exit 1
|
|
@@ -2759,11 +2766,11 @@ EOT
|
|
|
2759
2766
|
return results['servers'][0]
|
|
2760
2767
|
end
|
|
2761
2768
|
|
|
2762
|
-
def find_host_by_name_or_id(val)
|
|
2769
|
+
def find_host_by_name_or_id(val, include_tenants=false)
|
|
2763
2770
|
if val.to_s =~ /\A\d{1,}\Z/
|
|
2764
2771
|
return find_host_by_id(val)
|
|
2765
2772
|
else
|
|
2766
|
-
return find_host_by_name(val)
|
|
2773
|
+
return find_host_by_name(val, include_tenants)
|
|
2767
2774
|
end
|
|
2768
2775
|
end
|
|
2769
2776
|
|
|
@@ -150,6 +150,13 @@ class Morpheus::Cli::Instances
|
|
|
150
150
|
options[:details] = true
|
|
151
151
|
params['details'] = true # get more data from server this way
|
|
152
152
|
end
|
|
153
|
+
opts.on('--include-tenants','--include-tenants', "Include sub tenant instances") do
|
|
154
|
+
options[:include_tenants] = true
|
|
155
|
+
params['includeTenants'] = true
|
|
156
|
+
end
|
|
157
|
+
opts.on('--tenant TENANT', String, "Tenant Name or ID" ) do |val|
|
|
158
|
+
options[:tenant] = val
|
|
159
|
+
end
|
|
153
160
|
build_standard_list_options(opts, options)
|
|
154
161
|
opts.footer = "List instances."
|
|
155
162
|
end
|
|
@@ -204,6 +211,19 @@ class Morpheus::Cli::Instances
|
|
|
204
211
|
end
|
|
205
212
|
end
|
|
206
213
|
|
|
214
|
+
if options[:tenant]
|
|
215
|
+
if options[:tenant].to_s !~ /\A\d{1,}\Z/
|
|
216
|
+
account = find_account_by_name_or_id(options[:tenant])
|
|
217
|
+
if account.nil?
|
|
218
|
+
return 1, "Tenant not found by name: #{options[:tenant]}"
|
|
219
|
+
else
|
|
220
|
+
params['tenantId'] = account['id']
|
|
221
|
+
end
|
|
222
|
+
else
|
|
223
|
+
params['tenantId'] = options[:tenant]
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
207
227
|
@instances_interface.setopts(options)
|
|
208
228
|
if options[:dry_run]
|
|
209
229
|
print_dry_run @instances_interface.dry.list(params)
|
|
@@ -241,6 +261,7 @@ class Morpheus::Cli::Instances
|
|
|
241
261
|
if options[:owner]
|
|
242
262
|
subtitles << "Created By: #{options[:owner]}"
|
|
243
263
|
end
|
|
264
|
+
|
|
244
265
|
subtitles += parse_list_subtitles(options)
|
|
245
266
|
print_h1 title, subtitles, options
|
|
246
267
|
if instances.empty?
|
|
@@ -279,7 +300,7 @@ class Morpheus::Cli::Instances
|
|
|
279
300
|
connection: format_instance_connection_string(instance),
|
|
280
301
|
environment: instance['instanceContext'],
|
|
281
302
|
user: (instance['owner'] ? (instance['owner']['username'] || instance['owner']['id']) : (instance['createdBy'].is_a?(Hash) ? instance['createdBy']['username'] : instance['createdBy'])),
|
|
282
|
-
tenant: (instance['
|
|
303
|
+
tenant: (instance['tenant'] ? instance['tenant']['name'] : ''),
|
|
283
304
|
nodes: instance['containers'].count,
|
|
284
305
|
status: format_instance_status(instance, cyan),
|
|
285
306
|
type: instance['instanceType']['name'],
|
|
@@ -294,18 +315,20 @@ class Morpheus::Cli::Instances
|
|
|
294
315
|
}
|
|
295
316
|
row
|
|
296
317
|
}
|
|
297
|
-
columns = [:id, {:name => {:max_width => 50}}, :labels, :group, :cloud,
|
|
318
|
+
columns = [:id, {:name => {:max_width => 50}}, :labels, :tenant, :group, :cloud,
|
|
298
319
|
:type, :version, :environment, :plan,
|
|
299
320
|
{:created => {:display_name => "CREATED"}},
|
|
300
|
-
# {:tenant => {:display_name => "TENANT"}},
|
|
301
321
|
{:user => {:display_name => "OWNER", :max_width => 20}},
|
|
302
|
-
:nodes, {:connection => {:max_width => 30}}, :status, :cpu, :memory, :storage]
|
|
322
|
+
:nodes, {:connection => {:max_width => 30}}, :status, :cpu, :memory, :storage].compact
|
|
303
323
|
# custom pretty table columns ... this is handled in as_pretty_table now(),
|
|
304
324
|
# todo: remove all these.. and try to always pass rows as the json data itself..
|
|
305
325
|
if options[:details] != true
|
|
306
326
|
columns.delete(:labels)
|
|
307
327
|
columns.delete(:plan)
|
|
308
328
|
end
|
|
329
|
+
if !options[:include_tenants] && !options[:tenant]
|
|
330
|
+
columns.delete(:tenant)
|
|
331
|
+
end
|
|
309
332
|
print cyan
|
|
310
333
|
print as_pretty_table(rows, columns, options)
|
|
311
334
|
print reset
|
|
@@ -1337,6 +1360,9 @@ class Morpheus::Cli::Instances
|
|
|
1337
1360
|
opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
|
|
1338
1361
|
options[:refresh_until_status] = val.to_s.downcase
|
|
1339
1362
|
end
|
|
1363
|
+
opts.on('--include-tenants','--include-tenants', "Include sub tenant instances when finding instance by name") do
|
|
1364
|
+
options[:include_tenants] = true
|
|
1365
|
+
end
|
|
1340
1366
|
# opts.on( nil, '--threshold', "Alias for --scaling" ) do
|
|
1341
1367
|
# options[:include_scaling] = true
|
|
1342
1368
|
# end
|
|
@@ -1369,7 +1395,7 @@ class Morpheus::Cli::Instances
|
|
|
1369
1395
|
end
|
|
1370
1396
|
instance = nil
|
|
1371
1397
|
if id.to_s !~ /\A\d{1,}\Z/
|
|
1372
|
-
instance = find_instance_by_name_or_id(id)
|
|
1398
|
+
instance = find_instance_by_name_or_id(id, options[:include_tenants])
|
|
1373
1399
|
return 1, "Instance not found by name #{id}" if instance.nil?
|
|
1374
1400
|
id = instance['id']
|
|
1375
1401
|
end
|
|
@@ -1463,7 +1489,7 @@ class Morpheus::Cli::Instances
|
|
|
1463
1489
|
it['createdBy'] ? (it['createdBy']['username'] || it['createdBy']['id']) : ''
|
|
1464
1490
|
end
|
|
1465
1491
|
},
|
|
1466
|
-
|
|
1492
|
+
"Tenant" => lambda {|it| it['tenant'] ? it['tenant']['name'] : '' },
|
|
1467
1493
|
"Apps" => lambda {|it| anded_list(it['apps'] ? it['apps'].collect {|app| app['name'] } : [])},
|
|
1468
1494
|
"Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
|
1469
1495
|
# "Last Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
|
|
@@ -4,13 +4,14 @@ class Morpheus::Cli::StorageVolumes
|
|
|
4
4
|
include Morpheus::Cli::CliCommand
|
|
5
5
|
include Morpheus::Cli::RestCommand
|
|
6
6
|
include Morpheus::Cli::StorageVolumesHelper
|
|
7
|
+
include Morpheus::Cli::AccountsHelper
|
|
7
8
|
|
|
8
9
|
set_command_name :'storage-volumes'
|
|
9
10
|
set_command_description "View and manage storage volumes."
|
|
10
|
-
register_subcommands :list, :get, :add, :remove, :resize
|
|
11
|
+
register_subcommands :list, :get, :add, :remove, :resize, :update_tenant
|
|
11
12
|
|
|
12
13
|
# RestCommand settings
|
|
13
|
-
register_interfaces :storage_volumes, :storage_volume_types
|
|
14
|
+
register_interfaces :storage_volumes, :storage_volume_types, :accounts
|
|
14
15
|
set_rest_has_type true
|
|
15
16
|
|
|
16
17
|
protected
|
|
@@ -28,12 +29,28 @@ class Morpheus::Cli::StorageVolumes
|
|
|
28
29
|
opts.on('--category VALUE', String, "Filter by category") do |val|
|
|
29
30
|
params['category'] = val
|
|
30
31
|
end
|
|
32
|
+
opts.on('--include-tenants', '--include-tenants', "Include sub-tenant storage volumes (master tenant only)") do
|
|
33
|
+
options[:include_tenants] = true
|
|
34
|
+
params['includeTenants'] = true
|
|
35
|
+
end
|
|
36
|
+
opts.on('--tenant TENANT', String, "Filter by Tenant Name or ID (master tenant only)") do |val|
|
|
37
|
+
options[:tenant] = val
|
|
38
|
+
end
|
|
31
39
|
# build_standard_list_options(opts, options)
|
|
32
40
|
super
|
|
33
41
|
end
|
|
34
42
|
|
|
35
43
|
def parse_list_options!(args, options, params)
|
|
36
44
|
parse_parameter_as_resource_id!(:storage_server, options, params)
|
|
45
|
+
if options[:tenant]
|
|
46
|
+
if options[:tenant].to_s =~ /\A\d+\Z/
|
|
47
|
+
params['tenantId'] = options[:tenant]
|
|
48
|
+
else
|
|
49
|
+
account = find_account_by_name_or_id(options[:tenant])
|
|
50
|
+
return 1 if account.nil?
|
|
51
|
+
params['tenantId'] = account['id']
|
|
52
|
+
end
|
|
53
|
+
end
|
|
37
54
|
super
|
|
38
55
|
end
|
|
39
56
|
|
|
@@ -68,7 +85,7 @@ class Morpheus::Cli::StorageVolumes
|
|
|
68
85
|
def add_storage_volume_option_types()
|
|
69
86
|
[
|
|
70
87
|
{'fieldContext' => 'storageServer', 'fieldName' => 'id', 'fieldLabel' => 'Storage Server', 'type' => 'select', 'optionSource' => 'storageServers', 'optionParams' => {'createType' => 'block'}, 'required' => true},
|
|
71
|
-
{'fieldContext' => 'storageGroup', 'fieldName' => 'id', 'fieldLabel' => 'Storage Group', 'type' => 'select', 'optionSource' => 'storageGroups', 'required' =>
|
|
88
|
+
{'fieldContext' => 'storageGroup', 'fieldName' => 'id', 'fieldLabel' => 'Storage Group', 'type' => 'select', 'optionSource' => 'storageGroups', 'required' => false},
|
|
72
89
|
{'shorthand' => '-t', 'fieldName' => 'type', 'fieldLabel' => 'Storage Volume Type', 'type' => 'select', 'optionSource' => 'storageVolumeTypes', 'required' => true},
|
|
73
90
|
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
|
|
74
91
|
]
|
|
@@ -122,6 +139,64 @@ class Morpheus::Cli::StorageVolumes
|
|
|
122
139
|
end
|
|
123
140
|
end
|
|
124
141
|
|
|
142
|
+
def update_tenant(args)
|
|
143
|
+
options = {}
|
|
144
|
+
params = {}
|
|
145
|
+
tenant_id = nil
|
|
146
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
147
|
+
opts.banner = subcommand_usage("[volume] --tenant TENANT")
|
|
148
|
+
opts.on('--tenant TENANT', String, "Target Tenant (Account) Name or ID to transfer ownership to.") do |val|
|
|
149
|
+
tenant_id = val
|
|
150
|
+
end
|
|
151
|
+
build_standard_update_options(opts, options)
|
|
152
|
+
opts.footer = <<-EOT
|
|
153
|
+
Transfer ownership of a storage volume to another tenant.
|
|
154
|
+
[volume] is required. This is the name or id of a storage volume.
|
|
155
|
+
--tenant is required unless provided via --payload. This is the name or id of the target tenant.
|
|
156
|
+
Only an unattached volume may be transferred. All authorization and business-rule
|
|
157
|
+
validation is performed by the server; its message is shown verbatim on failure.
|
|
158
|
+
EOT
|
|
159
|
+
end
|
|
160
|
+
optparse.parse!(args)
|
|
161
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
162
|
+
# CLI-side validation: require a target tenant unless a full payload is supplied.
|
|
163
|
+
# All other authorization and business rules are validated server-side.
|
|
164
|
+
if !options[:payload] && tenant_id.nil?
|
|
165
|
+
raise_command_error "--tenant is required to transfer ownership.\n#{optparse}"
|
|
166
|
+
end
|
|
167
|
+
connect(options)
|
|
168
|
+
volume = find_volume_by_name_or_id(args[0])
|
|
169
|
+
return 1 if volume.nil?
|
|
170
|
+
id = volume['id']
|
|
171
|
+
passed_options = parse_passed_options(options)
|
|
172
|
+
payload = {}
|
|
173
|
+
if options[:payload]
|
|
174
|
+
payload = options[:payload]
|
|
175
|
+
payload.deep_merge!({storage_volume_object_key => passed_options}) unless passed_options.empty?
|
|
176
|
+
else
|
|
177
|
+
account = find_account_by_name_or_id(tenant_id)
|
|
178
|
+
return 1 if account.nil?
|
|
179
|
+
payload[storage_volume_object_key] = {'tenant' => {'id' => account['id']}}
|
|
180
|
+
payload.deep_merge!({storage_volume_object_key => passed_options}) unless passed_options.empty?
|
|
181
|
+
end
|
|
182
|
+
@storage_volumes_interface.setopts(options)
|
|
183
|
+
if options[:dry_run]
|
|
184
|
+
print_dry_run @storage_volumes_interface.dry.update_tenant(id, payload)
|
|
185
|
+
return 0, nil
|
|
186
|
+
end
|
|
187
|
+
json_response = @storage_volumes_interface.update_tenant(id, payload)
|
|
188
|
+
render_response(json_response, options, storage_volume_object_key) do
|
|
189
|
+
record = json_response[storage_volume_object_key]
|
|
190
|
+
owner_name = (record && record['owner'] ? record['owner']['name'] : (record && record['account'] ? record['account']['name'] : tenant_id))
|
|
191
|
+
print_green_success "Transferred storage volume #{record ? record['name'] : id} to tenant #{owner_name}"
|
|
192
|
+
print_h1 rest_label, [], options
|
|
193
|
+
print cyan
|
|
194
|
+
print_description_list(storage_volume_column_definitions(options), record, options)
|
|
195
|
+
print reset,"\n"
|
|
196
|
+
end
|
|
197
|
+
return 0, nil
|
|
198
|
+
end
|
|
199
|
+
|
|
125
200
|
def find_volume_by_id(id)
|
|
126
201
|
begin
|
|
127
202
|
json_response = @storage_volumes_interface.get(id.to_i)
|