morpheus-cli 8.0.7 → 8.0.8
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 +1 -1
- data/lib/morpheus/api/clouds_interface.rb +35 -0
- data/lib/morpheus/api/clusters_interface.rb +30 -0
- data/lib/morpheus/api/servers_interface.rb +7 -0
- data/lib/morpheus/cli/commands/clouds.rb +348 -0
- data/lib/morpheus/cli/commands/clusters.rb +361 -1
- data/lib/morpheus/cli/commands/hosts.rb +144 -20
- data/lib/morpheus/cli/commands/instances.rb +76 -21
- data/lib/morpheus/cli/commands/snapshots.rb +4 -0
- data/lib/morpheus/cli/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df49518b39388f8a75824156a31bff7fb8abcf4545a9f5af29bf6eab4eab6a79
|
4
|
+
data.tar.gz: c82269904d1de5f01a487ebad5bd6ac2dd110a0c2e2b183142dfb42828c5edec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b64913eef407dd0180526e67095182b89517fa2863ee8bce319ba77fb1c1ca25d0898299ebebd6072dc9fcbd19a77f8d3f4fef3298563e24bd37121fb8ae6c75
|
7
|
+
data.tar.gz: 6569d731faabe620597d94d20f78ffde2b5599934cd2b5bdb7153e86ce6cbd8359b83e0ff97a61d1d8ff6b8c4c8c6fc809a82e72ef39da5514859800e8388e2a
|
data/Dockerfile
CHANGED
@@ -2,6 +2,10 @@ require 'morpheus/api/api_client'
|
|
2
2
|
|
3
3
|
class Morpheus::CloudsInterface < Morpheus::APIClient
|
4
4
|
|
5
|
+
def base_path
|
6
|
+
"/api/zones"
|
7
|
+
end
|
8
|
+
|
5
9
|
def cloud_types(params={})
|
6
10
|
url = "#{@base_url}/api/zone-types"
|
7
11
|
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
@@ -137,4 +141,35 @@ class Morpheus::CloudsInterface < Morpheus::APIClient
|
|
137
141
|
end
|
138
142
|
execute(method: :post, url: url, headers: headers, payload: payload)
|
139
143
|
end
|
144
|
+
|
145
|
+
def list_affinity_groups(id, params={})
|
146
|
+
url = "#{base_path}/#{id}/affinity-groups"
|
147
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
148
|
+
execute(method: :get, url: url, headers: headers)
|
149
|
+
end
|
150
|
+
|
151
|
+
def get_affinity_group(id, affinity_group_id, params={})
|
152
|
+
url = "#{base_path}/#{id}/affinity-groups/#{affinity_group_id}"
|
153
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
154
|
+
execute(method: :get, url: url, headers: headers)
|
155
|
+
end
|
156
|
+
|
157
|
+
def create_affinity_group(id, payload)
|
158
|
+
url = "#{base_path}/#{id}/affinity-groups"
|
159
|
+
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
160
|
+
execute(method: :post, url: url, headers: headers, payload: payload.to_json)
|
161
|
+
end
|
162
|
+
|
163
|
+
def update_affinity_group(id, affinity_group_id, payload)
|
164
|
+
url = "#{base_path}/#{id}/affinity-groups/#{affinity_group_id}"
|
165
|
+
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
166
|
+
execute(method: :put, url: url, headers: headers, payload: payload.to_json)
|
167
|
+
end
|
168
|
+
|
169
|
+
def destroy_affinity_group(id, affinity_group_id, params={})
|
170
|
+
url = "#{base_path}/#{id}/affinity-groups/#{affinity_group_id}"
|
171
|
+
headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
172
|
+
execute(method: :delete, url: url, headers: headers)
|
173
|
+
end
|
174
|
+
|
140
175
|
end
|
@@ -353,4 +353,34 @@ class Morpheus::ClustersInterface < Morpheus::APIClient
|
|
353
353
|
execute(method: :get, url: url, headers: headers)
|
354
354
|
end
|
355
355
|
|
356
|
+
def list_affinity_groups(id, params={})
|
357
|
+
url = "#{base_path}/#{id}/affinity-groups"
|
358
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
359
|
+
execute(method: :get, url: url, headers: headers)
|
360
|
+
end
|
361
|
+
|
362
|
+
def get_affinity_group(id, affinity_group_id, params={})
|
363
|
+
url = "#{base_path}/#{id}/affinity-groups/#{affinity_group_id}"
|
364
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
365
|
+
execute(method: :get, url: url, headers: headers)
|
366
|
+
end
|
367
|
+
|
368
|
+
def create_affinity_group(id, payload)
|
369
|
+
url = "#{base_path}/#{id}/affinity-groups"
|
370
|
+
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
371
|
+
execute(method: :post, url: url, headers: headers, payload: payload.to_json)
|
372
|
+
end
|
373
|
+
|
374
|
+
def update_affinity_group(id, affinity_group_id, payload)
|
375
|
+
url = "#{base_path}/#{id}/affinity-groups/#{affinity_group_id}"
|
376
|
+
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
377
|
+
execute(method: :put, url: url, headers: headers, payload: payload.to_json)
|
378
|
+
end
|
379
|
+
|
380
|
+
def destroy_affinity_group(id, affinity_group_id, params={})
|
381
|
+
url = "#{base_path}/#{id}/affinity-groups/#{affinity_group_id}"
|
382
|
+
headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
383
|
+
execute(method: :delete, url: url, headers: headers)
|
384
|
+
end
|
385
|
+
|
356
386
|
end
|
@@ -212,5 +212,12 @@ class Morpheus::ServersInterface < Morpheus::APIClient
|
|
212
212
|
execute(opts)
|
213
213
|
end
|
214
214
|
|
215
|
+
def snapshot(serverId, payload = {}, params = {})
|
216
|
+
url = "#{@base_url}/api/servers/#{serverId}/snapshot"
|
217
|
+
headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
218
|
+
opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
|
219
|
+
execute(opts)
|
220
|
+
end
|
221
|
+
|
215
222
|
|
216
223
|
end
|
@@ -11,6 +11,7 @@ class Morpheus::Cli::Clouds
|
|
11
11
|
alias_subcommand :'get-type', :type
|
12
12
|
register_subcommands :wiki, :update_wiki
|
13
13
|
register_subcommands({:'update-logo' => :update_logo,:'update-dark-logo' => :update_dark_logo})
|
14
|
+
register_subcommands :list_affinity_groups, :get_affinity_group, :update_affinity_group, :add_affinity_group, :remove_affinity_group
|
14
15
|
#register_subcommands :firewall_disable, :firewall_enable
|
15
16
|
alias_subcommand :details, :get
|
16
17
|
set_default_subcommand :list
|
@@ -1149,6 +1150,331 @@ EOT
|
|
1149
1150
|
end
|
1150
1151
|
end
|
1151
1152
|
|
1153
|
+
def list_affinity_groups(args)
|
1154
|
+
options = {}
|
1155
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1156
|
+
opts.banner = subcommand_usage( "[cloud]")
|
1157
|
+
build_standard_list_options(opts, options)
|
1158
|
+
opts.footer = "List affinity groups for a cloud.\n" +
|
1159
|
+
"[cloud] is required. This is the name or id of an existing cloud."
|
1160
|
+
end
|
1161
|
+
|
1162
|
+
optparse.parse!(args)
|
1163
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
1164
|
+
connect(options)
|
1165
|
+
|
1166
|
+
cloud = find_cloud_by_name_or_id(args[0])
|
1167
|
+
return 1 if cloud.nil?
|
1168
|
+
params = {}
|
1169
|
+
params.merge!(parse_list_options(options))
|
1170
|
+
json_response = @clouds_interface.list_affinity_groups(cloud['id'], params)
|
1171
|
+
render_response(json_response, options, 'affinityGroups') do
|
1172
|
+
affinity_groups = json_response['affinityGroups']
|
1173
|
+
print_h1 "Morpheus Cloud Affinity Groups: #{cloud['name']}", parse_list_subtitles(options)
|
1174
|
+
if affinity_groups.empty?
|
1175
|
+
print cyan,"No affinity groups found.",reset,"\n"
|
1176
|
+
else
|
1177
|
+
columns = {
|
1178
|
+
"ID" => 'id',
|
1179
|
+
"Name" => 'name',
|
1180
|
+
"Type" => lambda {|it| format_affinity_type(it['affinityType']) },
|
1181
|
+
"Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
|
1182
|
+
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
1183
|
+
# "Servers" => lambda {|it| it['serverCount'] },
|
1184
|
+
# "Source" => lambda {|it| it['source'] },
|
1185
|
+
}.upcase_keys!
|
1186
|
+
print as_pretty_table(affinity_groups, columns, options)
|
1187
|
+
print_results_pagination(json_response)
|
1188
|
+
end
|
1189
|
+
print reset,"\n"
|
1190
|
+
end
|
1191
|
+
return 0, nil
|
1192
|
+
end
|
1193
|
+
|
1194
|
+
def get_affinity_group(args)
|
1195
|
+
options = {}
|
1196
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1197
|
+
opts.banner = subcommand_usage( "[cloud] [affinity group]")
|
1198
|
+
build_standard_get_options(opts, options)
|
1199
|
+
opts.footer = "Get details about a cloud affinity group.\n" +
|
1200
|
+
"[cloud] is required. This is the name or id of an existing cloud.\n" +
|
1201
|
+
"[affinity group] is required. This is the name or id of an existing affinity group."
|
1202
|
+
end
|
1203
|
+
optparse.parse!(args)
|
1204
|
+
verify_args!(args:args, optparse:optparse, count:2)
|
1205
|
+
connect(options)
|
1206
|
+
|
1207
|
+
cloud = find_cloud_by_name_or_id(args[0])
|
1208
|
+
return 1 if cloud.nil?
|
1209
|
+
# this finds the affinity group in the cloud api response, then fetches it by ID
|
1210
|
+
affinity_group = find_cloud_affinity_group_by_name_or_id(cloud['id'], args[1])
|
1211
|
+
if affinity_group.nil?
|
1212
|
+
print_red_alert "Affinity Group not found for '#{args[1]}'"
|
1213
|
+
exit 1
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
params = {}
|
1217
|
+
params.merge!(parse_query_options(options))
|
1218
|
+
@clouds_interface.setopts(options)
|
1219
|
+
if options[:dry_run]
|
1220
|
+
print_dry_run @clouds_interface.dry.get_affinity_group(cloud['id'], affinity_group['id'], params)
|
1221
|
+
return
|
1222
|
+
end
|
1223
|
+
json_response = @clouds_interface.get_affinity_group(cloud['id'], affinity_group['id'], params)
|
1224
|
+
render_response(json_response, options, 'affinityGroup') do
|
1225
|
+
affinity_group = json_response['affinityGroup']
|
1226
|
+
print_h1 "Affinity Group Details", [], options
|
1227
|
+
columns = {
|
1228
|
+
"ID" => 'id',
|
1229
|
+
"Name" => 'name',
|
1230
|
+
"Type" => lambda {|it| format_affinity_type(it['affinityType']) },
|
1231
|
+
"Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
|
1232
|
+
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
1233
|
+
"Servers" => lambda {|it| it['servers'].size() },
|
1234
|
+
"Source" => lambda {|it| it['source'] },
|
1235
|
+
"Active" => lambda {|it| format_boolean(it['active']) },
|
1236
|
+
}
|
1237
|
+
print_description_list(columns, affinity_group)
|
1238
|
+
if affinity_group['servers'].size > 0
|
1239
|
+
print_h2 "Servers", options
|
1240
|
+
print as_pretty_table(affinity_group['servers'], [:id, :name], options)
|
1241
|
+
end
|
1242
|
+
print reset,"\n"
|
1243
|
+
end
|
1244
|
+
return 0, nil
|
1245
|
+
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
def add_affinity_group(args)
|
1249
|
+
options = {}
|
1250
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1251
|
+
opts.banner = subcommand_usage( "[cloud] [name] [options]")
|
1252
|
+
build_option_type_options(opts, options, add_cloud_affinity_group_option_types)
|
1253
|
+
# opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
|
1254
|
+
# options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
1255
|
+
# end
|
1256
|
+
# opts.on(nil, '--no-refresh', "Do not refresh" ) do
|
1257
|
+
# options[:no_refresh] = true
|
1258
|
+
# end
|
1259
|
+
build_standard_add_options(opts, options)
|
1260
|
+
opts.footer = "Add affinity group to a cloud.\n" +
|
1261
|
+
"[cloud] is required. This is the name or id of an existing cloud.\n" +
|
1262
|
+
"[name] is required. This is the name of the new affinity group."
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
optparse.parse!(args)
|
1266
|
+
verify_args!(args:args, optparse:optparse, min:1, max:2)
|
1267
|
+
connect(options)
|
1268
|
+
|
1269
|
+
begin
|
1270
|
+
cloud = find_cloud_by_name_or_id(args[0])
|
1271
|
+
return 1 if cloud.nil?
|
1272
|
+
if args[1]
|
1273
|
+
options[:options]['name'] = args[1]
|
1274
|
+
end
|
1275
|
+
if options[:payload]
|
1276
|
+
payload = options[:payload]
|
1277
|
+
# support -O OPTION switch on top of --payload
|
1278
|
+
if options[:options]
|
1279
|
+
payload ||= {}
|
1280
|
+
payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
|
1281
|
+
end
|
1282
|
+
else
|
1283
|
+
options[:params] ||= {}
|
1284
|
+
options[:params].merge!({:cloudId => cloud['id'],:zoneId => cloud['id']})
|
1285
|
+
option_types = add_cloud_affinity_group_option_types
|
1286
|
+
affinity_group = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client, options[:params])
|
1287
|
+
|
1288
|
+
# affinity_group_type = find_affinity_group_type_by_code(affinity_group['affinityGroupType'])
|
1289
|
+
# affinity_group['affinityGroupType'] = {id:affinity_group_type['id']}
|
1290
|
+
|
1291
|
+
# # affinity_group type options
|
1292
|
+
# unless affinity_group_type['optionTypes'].empty?
|
1293
|
+
# affinity_group.merge!(Morpheus::Cli::OptionTypes.prompt(affinity_group_type['optionTypes'], options[:options].deep_merge({:context_map => {'domain' => ''}, :checkbox_as_boolean => true}), @api_client, options[:params]))
|
1294
|
+
# end
|
1295
|
+
|
1296
|
+
# perms
|
1297
|
+
perms = prompt_permissions(options.merge({:for_affinity_group => true}), ['plans', 'groupDefaults'])
|
1298
|
+
|
1299
|
+
affinity_group['resourcePermissions'] = perms['resourcePermissions'] unless perms['resourcePermissions'].nil?
|
1300
|
+
affinity_group['tenants'] = perms['tenantPermissions'] unless perms['tenantPermissions'].nil?
|
1301
|
+
affinity_group['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
|
1302
|
+
|
1303
|
+
payload = {'affinityGroup' => affinity_group}
|
1304
|
+
end
|
1305
|
+
|
1306
|
+
@clouds_interface.setopts(options)
|
1307
|
+
if options[:dry_run]
|
1308
|
+
print_dry_run @clouds_interface.dry.create_affinity_group(cloud['id'], payload)
|
1309
|
+
return
|
1310
|
+
end
|
1311
|
+
json_response = @clouds_interface.create_affinity_group(cloud['id'], payload)
|
1312
|
+
if options[:json]
|
1313
|
+
puts as_json(json_response)
|
1314
|
+
else
|
1315
|
+
if json_response['success']
|
1316
|
+
if json_response['msg'] == nil
|
1317
|
+
print_green_success "Adding affinity group to cloud #{cloud['name']}"
|
1318
|
+
else
|
1319
|
+
print_green_success json_response['msg']
|
1320
|
+
end
|
1321
|
+
execution_id = json_response['executionId']
|
1322
|
+
if !options[:no_refresh] && execution_id
|
1323
|
+
wait_for_execution_request(json_response['executionId'], options.merge({waiting_status:['new', 'pending', 'executing']}))
|
1324
|
+
end
|
1325
|
+
else
|
1326
|
+
print_red_alert "Failed to create cloud affinity group #{json_response['msg']}"
|
1327
|
+
end
|
1328
|
+
end
|
1329
|
+
return 0
|
1330
|
+
rescue RestClient::Exception => e
|
1331
|
+
print_rest_exception(e, options)
|
1332
|
+
exit 1
|
1333
|
+
end
|
1334
|
+
end
|
1335
|
+
|
1336
|
+
def update_affinity_group(args)
|
1337
|
+
options = {}
|
1338
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1339
|
+
opts.banner = subcommand_usage( "[cloud] [affinity group] [options]")
|
1340
|
+
opts.on('--active [on|off]', String, "Enable affinity group") do |val|
|
1341
|
+
options[:active] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
|
1342
|
+
end
|
1343
|
+
add_perms_options(opts, options, ['groupDefaults'])
|
1344
|
+
build_standard_update_options(opts, options)
|
1345
|
+
opts.footer = "Update a cloud affinity group.\n" +
|
1346
|
+
"[cloud] is required. This is the name or id of an existing cloud.\n" +
|
1347
|
+
"[affinity group] is required. This is the name or id of an existing affinity group."
|
1348
|
+
end
|
1349
|
+
|
1350
|
+
optparse.parse!(args)
|
1351
|
+
if args.count != 2
|
1352
|
+
raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
|
1353
|
+
end
|
1354
|
+
connect(options)
|
1355
|
+
|
1356
|
+
begin
|
1357
|
+
cloud = find_cloud_by_name_or_id(args[0])
|
1358
|
+
return 1 if cloud.nil?
|
1359
|
+
affinity_group = find_cloud_affinity_group_by_name_or_id(cloud['id'], args[1])
|
1360
|
+
if affinity_group.nil?
|
1361
|
+
print_red_alert "Affinity Group not found by '#{args[1]}'"
|
1362
|
+
exit 1
|
1363
|
+
end
|
1364
|
+
payload = nil
|
1365
|
+
if options[:payload]
|
1366
|
+
payload = options[:payload]
|
1367
|
+
# support -O OPTION switch on top of everything
|
1368
|
+
if options[:options]
|
1369
|
+
payload.deep_merge!({'affinityGroup' => options[:options].reject {|k,v| k.is_a?(Symbol) }})
|
1370
|
+
end
|
1371
|
+
else
|
1372
|
+
payload = {'affinityGroup' => {}}
|
1373
|
+
payload['affinityGroup']['active'] = options[:active].nil? ? (Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'description' => 'Active', 'defaultValue' => true}], options[:options], @api_client))['active'] == 'on' : options[:active]
|
1374
|
+
|
1375
|
+
perms = prompt_permissions(options.merge({:available_plans => namespace_service_plans}), affinity_group['owner']['id'] == current_user['accountId'] ? ['plans', 'groupDefaults'] : ['plans', 'groupDefaults', 'visibility', 'tenants'])
|
1376
|
+
perms_payload = {}
|
1377
|
+
perms_payload['resourcePermissions'] = perms['resourcePermissions'] if !perms['resourcePermissions'].nil?
|
1378
|
+
perms_payload['tenantPermissions'] = perms['tenantPermissions'] if !perms['tenantPermissions'].nil?
|
1379
|
+
|
1380
|
+
payload['affinityGroup']['permissions'] = perms_payload
|
1381
|
+
payload['affinityGroup']['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
|
1382
|
+
|
1383
|
+
# support -O OPTION switch on top of everything
|
1384
|
+
if options[:options]
|
1385
|
+
payload.deep_merge!({'affinityGroup' => options[:options].reject {|k,v| k.is_a?(Symbol) }})
|
1386
|
+
end
|
1387
|
+
|
1388
|
+
if payload['affinityGroup'].nil? || payload['affinityGroup'].empty?
|
1389
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
1390
|
+
end
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
@clouds_interface.setopts(options)
|
1394
|
+
if options[:dry_run]
|
1395
|
+
print_dry_run @clouds_interface.dry.update_affinity_group(cloud['id'], affinity_group['id'], payload)
|
1396
|
+
return
|
1397
|
+
end
|
1398
|
+
json_response = @clouds_interface.update_affinity_group(cloud['id'], affinity_group['id'], payload)
|
1399
|
+
if options[:json]
|
1400
|
+
puts as_json(json_response)
|
1401
|
+
elsif !options[:quiet]
|
1402
|
+
affinity_group = json_response['affinityGroup']
|
1403
|
+
print_green_success "Updated affinity group #{affinity_group['name']}"
|
1404
|
+
#get_args = [cloud["id"], affinity_group["id"]] + (options[:remote] ? ["-r",options[:remote]] : [])
|
1405
|
+
#get_namespace(get_args)
|
1406
|
+
end
|
1407
|
+
return 0
|
1408
|
+
rescue RestClient::Exception => e
|
1409
|
+
print_rest_exception(e, options)
|
1410
|
+
exit 1
|
1411
|
+
end
|
1412
|
+
end
|
1413
|
+
|
1414
|
+
def remove_affinity_group(args)
|
1415
|
+
default_refresh_interval = 5
|
1416
|
+
params = {}
|
1417
|
+
options = {}
|
1418
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1419
|
+
opts.banner = subcommand_usage("[cloud] [affinity group]")
|
1420
|
+
# opts.on( '-f', '--force', "Force Delete" ) do
|
1421
|
+
# params[:force] = 'on'
|
1422
|
+
# end
|
1423
|
+
# opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
|
1424
|
+
# options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
1425
|
+
# end
|
1426
|
+
# opts.on(nil, '--no-refresh', "Do not refresh" ) do
|
1427
|
+
# options[:no_refresh] = true
|
1428
|
+
# end
|
1429
|
+
build_standard_remove_options(opts, options)
|
1430
|
+
opts.footer = "Delete an affinity group from a cloud.\n" +
|
1431
|
+
"[cloud] is required. This is the name or id of an existing cloud.\n" +
|
1432
|
+
"[affinity group] is required. This is the name or id of an existing affinity group."
|
1433
|
+
end
|
1434
|
+
optparse.parse!(args)
|
1435
|
+
verify_args!(args:args, optparse:optparse, count:2)
|
1436
|
+
connect(options)
|
1437
|
+
params.merge!(parse_query_options(options))
|
1438
|
+
|
1439
|
+
cloud = find_cloud_by_name_or_id(args[0])
|
1440
|
+
return 1 if cloud.nil?
|
1441
|
+
|
1442
|
+
affinity_group_id = args[1]
|
1443
|
+
if affinity_group_id.empty?
|
1444
|
+
raise_command_error "missing required worker parameter"
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
affinity_group = find_cloud_affinity_group_by_name_or_id(cloud['id'], affinity_group_id)
|
1448
|
+
if affinity_group.nil?
|
1449
|
+
print_red_alert "Affinity Group not found for '#{affinity_group_id}'"
|
1450
|
+
return 1
|
1451
|
+
end
|
1452
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cloud affinity group '#{affinity_group['name'] || affinity_group['id']}'?", options)
|
1453
|
+
return 9, "aborted command"
|
1454
|
+
end
|
1455
|
+
|
1456
|
+
@clouds_interface.setopts(options)
|
1457
|
+
if options[:dry_run]
|
1458
|
+
print_dry_run @clouds_interface.dry.destroy_affinity_group(cloud['id'], affinity_group['id'], params)
|
1459
|
+
return
|
1460
|
+
end
|
1461
|
+
json_response = @clouds_interface.destroy_affinity_group(cloud['id'], affinity_group['id'], params)
|
1462
|
+
if options[:json]
|
1463
|
+
puts as_json(json_response)
|
1464
|
+
else
|
1465
|
+
if json_response['success']
|
1466
|
+
print_green_success "Removed affinity group #{affinity_group['name']}"
|
1467
|
+
execution_id = json_response['executionId']
|
1468
|
+
if !options[:no_refresh] && execution_id
|
1469
|
+
wait_for_execution_request(execution_id, options.merge({waiting_status:['new', 'pending', 'executing']}))
|
1470
|
+
end
|
1471
|
+
else
|
1472
|
+
print_red_alert "Failed to remove cloud affinity group #{json_response['msg']}"
|
1473
|
+
end
|
1474
|
+
end
|
1475
|
+
return 0, nil
|
1476
|
+
end
|
1477
|
+
|
1152
1478
|
private
|
1153
1479
|
|
1154
1480
|
def cloud_list_column_definitions(options)
|
@@ -1259,4 +1585,26 @@ EOT
|
|
1259
1585
|
end
|
1260
1586
|
end
|
1261
1587
|
|
1588
|
+
def find_cloud_affinity_group_by_name_or_id(cloud_id, val)
|
1589
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
1590
|
+
@clouds_interface.get_affinity_group(cloud_id, val)['affinityGroup'] rescue nil
|
1591
|
+
else
|
1592
|
+
@clouds_interface.list_affinity_groups(cloud_id, {name: val})['affinityGroups'][0]
|
1593
|
+
end
|
1594
|
+
end
|
1595
|
+
|
1596
|
+
def add_cloud_affinity_group_option_types
|
1597
|
+
[
|
1598
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
|
1599
|
+
{'fieldName' => 'affinityType', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Keep Separate', 'value' => 'KEEP_SEPARATE'}, {'name' => 'Keep Together', 'value' => 'KEEP_TOGETHER'}], 'description' => 'Choose affinity type.', 'required' => true, 'defaultValue' => 'KEEP_SEPARATE'},
|
1600
|
+
{'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'defaultValue' => true},
|
1601
|
+
{'fieldName' => 'pool.id', 'fieldLabel' => 'Cluster', 'type' => 'select', 'optionSourceType' => 'vmware', 'optionSource' => 'vmwareZonePoolClusters', 'description' => 'Select cluster for the affinity group.', 'required' => true},
|
1602
|
+
{'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiSelect', 'optionSource' => 'searchServers', 'description' => 'Select servers to be in the affinity group.'},
|
1603
|
+
]
|
1604
|
+
end
|
1605
|
+
|
1606
|
+
def format_affinity_type(affinity_type)
|
1607
|
+
affinity_type == "KEEP_SEPARATE" ? "Keep Separate" : "Keep Together"
|
1608
|
+
end
|
1609
|
+
|
1262
1610
|
end
|
@@ -28,6 +28,7 @@ class Morpheus::Cli::Clusters
|
|
28
28
|
register_subcommands :refresh
|
29
29
|
register_subcommands :list_replicasets, :list_daemonsets, :list_endpoints, :list_ingresses, :list_policies, :list_volumes, :list_volume_claims, :list_config_maps, :list_secrets
|
30
30
|
register_subcommands :get_pod, :get_deployment, :get_replicaset, :get_daemonset, :get_endpoint, :get_ingress, :get_policy, :get_volume_claim, :get_volume, :get_config_map, :get_secret, :get_stateful_set, :get_job, :get_service
|
31
|
+
register_subcommands :list_affinity_groups, :get_affinity_group, :update_affinity_group, :add_affinity_group, :remove_affinity_group
|
31
32
|
|
32
33
|
def connect(opts)
|
33
34
|
@api_client = establish_remote_appliance_connection(opts)
|
@@ -1379,7 +1380,12 @@ class Morpheus::Cli::Clusters
|
|
1379
1380
|
(['provisionType.vmware.host', 'provisionType.scvmm.host'].include?(type['code']) && cloud['config']['hideHostSelection'] == 'on') || # should this be truthy?
|
1380
1381
|
(type['fieldContext'] == 'instance.networkDomain' && type['fieldName'] == 'id')
|
1381
1382
|
} rescue [])
|
1382
|
-
|
1383
|
+
# strip server context
|
1384
|
+
option_type_list.each do |option_type|
|
1385
|
+
if option_type['fieldContext'] == 'server' || option_type['fieldContext'] == 'domain'
|
1386
|
+
option_type['fieldContext'] = nil
|
1387
|
+
end
|
1388
|
+
end
|
1383
1389
|
# remove metadata option_type , prompt manually for that field 'tags' instead of 'metadata'
|
1384
1390
|
#metadata_option_type = option_type_list.find {|type| type['fieldName'] == 'metadata' }
|
1385
1391
|
metadata_option_type = cluster_type['optionTypes'].find {|type| type['fieldName'] == 'metadata' }
|
@@ -3269,6 +3275,337 @@ class Morpheus::Cli::Clusters
|
|
3269
3275
|
end
|
3270
3276
|
end
|
3271
3277
|
|
3278
|
+
def list_affinity_groups(args)
|
3279
|
+
options = {}
|
3280
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3281
|
+
opts.banner = subcommand_usage( "[cluster]")
|
3282
|
+
build_standard_list_options(opts, options)
|
3283
|
+
opts.footer = "List affinity groups for a cluster.\n" +
|
3284
|
+
"[cluster] is required. This is the name or id of an existing cluster."
|
3285
|
+
end
|
3286
|
+
|
3287
|
+
optparse.parse!(args)
|
3288
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
3289
|
+
connect(options)
|
3290
|
+
|
3291
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
3292
|
+
return 1 if cluster.nil?
|
3293
|
+
|
3294
|
+
params = {}
|
3295
|
+
params.merge!(parse_list_options(options))
|
3296
|
+
@clusters_interface.setopts(options)
|
3297
|
+
if options[:dry_run]
|
3298
|
+
print_dry_run @clusters_interface.dry.list_affinity_groups(cluster['id'], params)
|
3299
|
+
return
|
3300
|
+
end
|
3301
|
+
json_response = @clusters_interface.list_affinity_groups(cluster['id'], params)
|
3302
|
+
render_response(json_response, options, 'affinityGroups') do
|
3303
|
+
affinity_groups = json_response['affinityGroups']
|
3304
|
+
print_h1 "Morpheus Cluster Affinity Groups: #{cluster['name']}", parse_list_subtitles(options), options
|
3305
|
+
if affinity_groups.empty?
|
3306
|
+
print cyan,"No affinity groups found.",reset,"\n"
|
3307
|
+
else
|
3308
|
+
columns = {
|
3309
|
+
"ID" => 'id',
|
3310
|
+
"Name" => 'name',
|
3311
|
+
"Type" => lambda {|it| format_affinity_type(it['affinityType']) },
|
3312
|
+
"Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
|
3313
|
+
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
3314
|
+
"Servers" => lambda {|it| it['servers'].size() },
|
3315
|
+
# "Source" => lambda {|it| it['source'] },
|
3316
|
+
}.upcase_keys!
|
3317
|
+
print as_pretty_table(affinity_groups, columns, options)
|
3318
|
+
print_results_pagination(json_response)
|
3319
|
+
end
|
3320
|
+
print reset,"\n"
|
3321
|
+
end
|
3322
|
+
return 0, nil
|
3323
|
+
end
|
3324
|
+
|
3325
|
+
def get_affinity_group(args)
|
3326
|
+
options = {}
|
3327
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3328
|
+
opts.banner = subcommand_usage( "[cluster] [affinity group]")
|
3329
|
+
build_standard_get_options(opts, options)
|
3330
|
+
opts.footer = "Get details about a cluster affinity group.\n" +
|
3331
|
+
"[cluster] is required. This is the name or id of an existing cluster.\n" +
|
3332
|
+
"[affinity group] is required. This is the name or id of an existing affinity group."
|
3333
|
+
end
|
3334
|
+
optparse.parse!(args)
|
3335
|
+
verify_args!(args:args, optparse:optparse, count:2)
|
3336
|
+
connect(options)
|
3337
|
+
|
3338
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
3339
|
+
return 1 if cluster.nil?
|
3340
|
+
|
3341
|
+
# this finds the affinity group in the cluster api response, then fetches it by ID
|
3342
|
+
affinity_group = find_cluster_affinity_group_by_name_or_id(cluster['id'], args[1])
|
3343
|
+
if affinity_group.nil?
|
3344
|
+
print_red_alert "Affinity Group not found for '#{args[1]}'"
|
3345
|
+
exit 1
|
3346
|
+
end
|
3347
|
+
|
3348
|
+
params = {}
|
3349
|
+
params.merge!(parse_query_options(options))
|
3350
|
+
@clusters_interface.setopts(options)
|
3351
|
+
if options[:dry_run]
|
3352
|
+
print_dry_run @clusters_interface.dry.get_affinity_group(cluster['id'], affinity_group['id'], params)
|
3353
|
+
return
|
3354
|
+
end
|
3355
|
+
json_response = @clusters_interface.get_affinity_group(cluster['id'], affinity_group['id'], params)
|
3356
|
+
render_response(json_response, options, 'affinityGroup') do
|
3357
|
+
affinity_group = json_response['affinityGroup']
|
3358
|
+
print_h1 "Affinity Group Details", [], options
|
3359
|
+
columns = {
|
3360
|
+
"ID" => 'id',
|
3361
|
+
"Name" => 'name',
|
3362
|
+
"Type" => lambda {|it| format_affinity_type(it['affinityType']) },
|
3363
|
+
"Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
|
3364
|
+
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
3365
|
+
"Servers" => lambda {|it| it['servers'].size() },
|
3366
|
+
"Source" => lambda {|it| it['source'] },
|
3367
|
+
"Active" => lambda {|it| format_boolean(it['active']) },
|
3368
|
+
}
|
3369
|
+
print_description_list(columns, affinity_group)
|
3370
|
+
if affinity_group['servers'].size > 0
|
3371
|
+
print_h2 "Servers", options
|
3372
|
+
print as_pretty_table(affinity_group['servers'], [:id, :name], options)
|
3373
|
+
end
|
3374
|
+
print reset,"\n"
|
3375
|
+
end
|
3376
|
+
return 0, nil
|
3377
|
+
end
|
3378
|
+
|
3379
|
+
def add_affinity_group(args)
|
3380
|
+
options = {}
|
3381
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3382
|
+
opts.banner = subcommand_usage( "[cluster] [name] [options]")
|
3383
|
+
build_option_type_options(opts, options, add_affinity_group_option_types)
|
3384
|
+
# opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
|
3385
|
+
# options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
3386
|
+
# end
|
3387
|
+
# opts.on(nil, '--no-refresh', "Do not refresh" ) do
|
3388
|
+
# options[:no_refresh] = true
|
3389
|
+
# end
|
3390
|
+
build_standard_add_options(opts, options)
|
3391
|
+
opts.footer = "Add affinity group to a cluster.\n" +
|
3392
|
+
"[cluster] is required. This is the name or id of an existing cluster.\n" +
|
3393
|
+
"[name] is required. This is the name of the new affinity group."
|
3394
|
+
end
|
3395
|
+
|
3396
|
+
optparse.parse!(args)
|
3397
|
+
verify_args!(args:args, optparse:optparse, min:1, max:2)
|
3398
|
+
connect(options)
|
3399
|
+
|
3400
|
+
begin
|
3401
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
3402
|
+
return 1 if cluster.nil?
|
3403
|
+
if args[1]
|
3404
|
+
options[:options]['name'] = args[1]
|
3405
|
+
end
|
3406
|
+
if options[:payload]
|
3407
|
+
payload = options[:payload]
|
3408
|
+
# support -O OPTION switch on top of --payload
|
3409
|
+
if options[:options]
|
3410
|
+
payload ||= {}
|
3411
|
+
payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
|
3412
|
+
end
|
3413
|
+
else
|
3414
|
+
options[:params] ||= {}
|
3415
|
+
options[:params].merge!({:serverGroupId => cluster['id']})
|
3416
|
+
option_types = add_affinity_group_option_types
|
3417
|
+
affinity_group = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client, options[:params])
|
3418
|
+
|
3419
|
+
# affinity_group_type = find_affinity_group_type_by_code(affinity_group['affinityGroupType'])
|
3420
|
+
# affinity_group['affinityGroupType'] = {id:affinity_group_type['id']}
|
3421
|
+
|
3422
|
+
# # affinity_group type options
|
3423
|
+
# unless affinity_group_type['optionTypes'].empty?
|
3424
|
+
# affinity_group.merge!(Morpheus::Cli::OptionTypes.prompt(affinity_group_type['optionTypes'], options[:options].deep_merge({:context_map => {'domain' => ''}, :checkbox_as_boolean => true}), @api_client, options[:params]))
|
3425
|
+
# end
|
3426
|
+
|
3427
|
+
# perms
|
3428
|
+
perms = prompt_permissions(options.merge({:for_affinity_group => true}), ['groupDefaults'])
|
3429
|
+
|
3430
|
+
affinity_group['resourcePermissions'] = perms['resourcePermissions'] unless perms['resourcePermissions'].nil?
|
3431
|
+
affinity_group['tenants'] = perms['tenantPermissions'] unless perms['tenantPermissions'].nil?
|
3432
|
+
affinity_group['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
|
3433
|
+
|
3434
|
+
payload = {'affinityGroup' => affinity_group}
|
3435
|
+
end
|
3436
|
+
|
3437
|
+
@clusters_interface.setopts(options)
|
3438
|
+
if options[:dry_run]
|
3439
|
+
print_dry_run @clusters_interface.dry.create_affinity_group(cluster['id'], payload)
|
3440
|
+
return
|
3441
|
+
end
|
3442
|
+
json_response = @clusters_interface.create_affinity_group(cluster['id'], payload)
|
3443
|
+
if options[:json]
|
3444
|
+
puts as_json(json_response)
|
3445
|
+
else
|
3446
|
+
if json_response['success']
|
3447
|
+
if json_response['msg'] == nil
|
3448
|
+
print_green_success "Adding affinity group to cluster #{cluster['name']}"
|
3449
|
+
else
|
3450
|
+
print_green_success json_response['msg']
|
3451
|
+
end
|
3452
|
+
execution_id = json_response['executionId']
|
3453
|
+
if !options[:no_refresh] && execution_id
|
3454
|
+
wait_for_execution_request(json_response['executionId'], options.merge({waiting_status:['new', 'pending', 'executing']}))
|
3455
|
+
end
|
3456
|
+
else
|
3457
|
+
print_red_alert "Failed to create cluster affinity group #{json_response['msg']}"
|
3458
|
+
end
|
3459
|
+
end
|
3460
|
+
return 0
|
3461
|
+
rescue RestClient::Exception => e
|
3462
|
+
print_rest_exception(e, options)
|
3463
|
+
exit 1
|
3464
|
+
end
|
3465
|
+
end
|
3466
|
+
|
3467
|
+
def update_affinity_group(args)
|
3468
|
+
options = {}
|
3469
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3470
|
+
opts.banner = subcommand_usage( "[cluster] [affinity group] [options]")
|
3471
|
+
opts.on('--active [on|off]', String, "Enable affinity group") do |val|
|
3472
|
+
options[:active] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
|
3473
|
+
end
|
3474
|
+
# add_perms_options(opts, options, ['groupDefaults'])
|
3475
|
+
build_standard_update_options(opts, options)
|
3476
|
+
opts.footer = "Update a cluster affinity group.\n" +
|
3477
|
+
"[cluster] is required. This is the name or id of an existing cluster.\n" +
|
3478
|
+
"[affinity group] is required. This is the name or id of an existing affinity group."
|
3479
|
+
end
|
3480
|
+
|
3481
|
+
optparse.parse!(args)
|
3482
|
+
if args.count != 2
|
3483
|
+
raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
|
3484
|
+
end
|
3485
|
+
connect(options)
|
3486
|
+
|
3487
|
+
begin
|
3488
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
3489
|
+
return 1 if cluster.nil?
|
3490
|
+
affinity_group = find_cluster_affinity_group_by_name_or_id(cluster['id'], args[1])
|
3491
|
+
if affinity_group.nil?
|
3492
|
+
print_red_alert "Affinity Group not found by '#{args[1]}'"
|
3493
|
+
exit 1
|
3494
|
+
end
|
3495
|
+
payload = nil
|
3496
|
+
if options[:payload]
|
3497
|
+
payload = options[:payload]
|
3498
|
+
# support -O OPTION switch on top of everything
|
3499
|
+
if options[:options]
|
3500
|
+
payload.deep_merge!({'affinityGroup' => options[:options].reject {|k,v| k.is_a?(Symbol) }})
|
3501
|
+
end
|
3502
|
+
else
|
3503
|
+
payload = {'affinityGroup' => {}}
|
3504
|
+
payload['affinityGroup']['active'] = options[:active].nil? ? (Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'description' => 'Active', 'defaultValue' => true}], options[:options], @api_client))['active'] == 'on' : options[:active]
|
3505
|
+
|
3506
|
+
perms = prompt_permissions(options.merge({:available_plans => namespace_service_plans}), affinity_group['owner']['id'] == current_user['accountId'] ? ['plans', 'groupDefaults'] : ['plans', 'groupDefaults', 'visibility', 'tenants'])
|
3507
|
+
perms_payload = {}
|
3508
|
+
perms_payload['resourcePermissions'] = perms['resourcePermissions'] if !perms['resourcePermissions'].nil?
|
3509
|
+
perms_payload['tenantPermissions'] = perms['tenantPermissions'] if !perms['tenantPermissions'].nil?
|
3510
|
+
|
3511
|
+
payload['affinityGroup']['permissions'] = perms_payload
|
3512
|
+
payload['affinityGroup']['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
|
3513
|
+
|
3514
|
+
# support -O OPTION switch on top of everything
|
3515
|
+
if options[:options]
|
3516
|
+
payload.deep_merge!({'affinityGroup' => options[:options].reject {|k,v| k.is_a?(Symbol) }})
|
3517
|
+
end
|
3518
|
+
|
3519
|
+
if payload['affinityGroup'].nil? || payload['affinityGroup'].empty?
|
3520
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
3521
|
+
end
|
3522
|
+
end
|
3523
|
+
|
3524
|
+
@clusters_interface.setopts(options)
|
3525
|
+
if options[:dry_run]
|
3526
|
+
print_dry_run @clusters_interface.dry.update_affinity_group(cluster['id'], affinity_group['id'], payload)
|
3527
|
+
return
|
3528
|
+
end
|
3529
|
+
json_response = @clusters_interface.update_affinity_group(cluster['id'], affinity_group['id'], payload)
|
3530
|
+
if options[:json]
|
3531
|
+
puts as_json(json_response)
|
3532
|
+
elsif !options[:quiet]
|
3533
|
+
affinity_group = json_response['affinityGroup']
|
3534
|
+
print_green_success "Updated affinity group #{affinity_group['name']}"
|
3535
|
+
#get_args = [cluster["id"], affinity_group["id"]] + (options[:remote] ? ["-r",options[:remote]] : [])
|
3536
|
+
#get_namespace(get_args)
|
3537
|
+
end
|
3538
|
+
return 0
|
3539
|
+
rescue RestClient::Exception => e
|
3540
|
+
print_rest_exception(e, options)
|
3541
|
+
exit 1
|
3542
|
+
end
|
3543
|
+
end
|
3544
|
+
|
3545
|
+
def remove_affinity_group(args)
|
3546
|
+
default_refresh_interval = 5
|
3547
|
+
params = {}
|
3548
|
+
options = {}
|
3549
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3550
|
+
opts.banner = subcommand_usage("[cluster] [affinity group]")
|
3551
|
+
# opts.on( '-f', '--force', "Force Delete" ) do
|
3552
|
+
# params[:force] = 'on'
|
3553
|
+
# end
|
3554
|
+
# opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
|
3555
|
+
# options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
3556
|
+
# end
|
3557
|
+
# opts.on(nil, '--no-refresh', "Do not refresh" ) do
|
3558
|
+
# options[:no_refresh] = true
|
3559
|
+
# end
|
3560
|
+
build_standard_remove_options(opts, options)
|
3561
|
+
opts.footer = "Delete an affinity group from a cluster.\n" +
|
3562
|
+
"[cluster] is required. This is the name or id of an existing cluster.\n" +
|
3563
|
+
"[affinity group] is required. This is the name or id of an existing affinity group."
|
3564
|
+
end
|
3565
|
+
optparse.parse!(args)
|
3566
|
+
verify_args!(args:args, optparse:optparse, count:2)
|
3567
|
+
connect(options)
|
3568
|
+
params.merge!(parse_query_options(options))
|
3569
|
+
|
3570
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
3571
|
+
return 1 if cluster.nil?
|
3572
|
+
|
3573
|
+
affinity_group_id = args[1]
|
3574
|
+
if affinity_group_id.empty?
|
3575
|
+
raise_command_error "missing required worker parameter"
|
3576
|
+
end
|
3577
|
+
|
3578
|
+
affinity_group = find_cluster_affinity_group_by_name_or_id(cluster['id'], affinity_group_id)
|
3579
|
+
if affinity_group.nil?
|
3580
|
+
print_red_alert "Affinity Group not found for '#{affinity_group_id}'"
|
3581
|
+
return 1
|
3582
|
+
end
|
3583
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster affinity group '#{affinity_group['name'] || affinity_group['id']}'?", options)
|
3584
|
+
return 9, "aborted command"
|
3585
|
+
end
|
3586
|
+
|
3587
|
+
@clusters_interface.setopts(options)
|
3588
|
+
if options[:dry_run]
|
3589
|
+
print_dry_run @clusters_interface.dry.destroy_affinity_group(cluster['id'], affinity_group['id'], params)
|
3590
|
+
return
|
3591
|
+
end
|
3592
|
+
json_response = @clusters_interface.destroy_affinity_group(cluster['id'], affinity_group['id'], params)
|
3593
|
+
if options[:json]
|
3594
|
+
puts as_json(json_response)
|
3595
|
+
else
|
3596
|
+
if json_response['success']
|
3597
|
+
print_green_success "Removed affinity group #{affinity_group['name']}"
|
3598
|
+
execution_id = json_response['executionId']
|
3599
|
+
if !options[:no_refresh] && execution_id
|
3600
|
+
wait_for_execution_request(execution_id, options.merge({waiting_status:['new', 'pending', 'executing']}))
|
3601
|
+
end
|
3602
|
+
else
|
3603
|
+
print_red_alert "Failed to remove cluster affinity_group #{json_response['msg']}"
|
3604
|
+
end
|
3605
|
+
end
|
3606
|
+
return 0, nil
|
3607
|
+
end
|
3608
|
+
|
3272
3609
|
def api_config(args)
|
3273
3610
|
options = {}
|
3274
3611
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
@@ -4866,4 +5203,27 @@ class Morpheus::Cli::Clusters
|
|
4866
5203
|
connect(options)
|
4867
5204
|
_list_container_groups(args, options, resource_type)
|
4868
5205
|
end
|
5206
|
+
|
5207
|
+
def find_cluster_affinity_group_by_name_or_id(cluster_id, val)
|
5208
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
5209
|
+
@clusters_interface.get_affinity_group(cluster_id, val)['affinityGroup'] rescue nil
|
5210
|
+
else
|
5211
|
+
@clusters_interface.list_affinity_groups(cluster_id, {name: val})['affinityGroups'][0]
|
5212
|
+
end
|
5213
|
+
end
|
5214
|
+
|
5215
|
+
def add_affinity_group_option_types
|
5216
|
+
[
|
5217
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
|
5218
|
+
{'fieldName' => 'affinityType', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Keep Separate', 'value' => 'KEEP_SEPARATE'}, {'name' => 'Keep Together', 'value' => 'KEEP_TOGETHER'}], 'description' => 'Choose affinity type.', 'required' => true, 'defaultValue' => 'KEEP_SEPARATE'},
|
5219
|
+
{'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'defaultValue' => true},
|
5220
|
+
# {'fieldName' => 'pool.id', 'fieldLabel' => 'Cluster', 'type' => 'select', 'optionSourceType' => 'vmware', 'optionSource' => 'vmwareZonePoolClusters', 'description' => 'Select cluster for the affinity group.', 'required' => true},
|
5221
|
+
{'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiSelect', 'optionSource' => 'searchServers', 'description' => 'Select servers to be in the affinity group.'},
|
5222
|
+
]
|
5223
|
+
end
|
5224
|
+
|
5225
|
+
def format_affinity_type(affinity_type)
|
5226
|
+
affinity_type == "KEEP_SEPARATE" ? "Keep Separate" : "Keep Together"
|
5227
|
+
end
|
5228
|
+
|
4869
5229
|
end
|
@@ -3,6 +3,7 @@ require 'morpheus/cli/cli_command'
|
|
3
3
|
class Morpheus::Cli::Hosts
|
4
4
|
include Morpheus::Cli::CliCommand
|
5
5
|
include Morpheus::Cli::AccountsHelper
|
6
|
+
include Morpheus::Cli::ProcessesHelper
|
6
7
|
include Morpheus::Cli::ProvisioningHelper
|
7
8
|
include Morpheus::Cli::LogsHelper
|
8
9
|
set_command_name :hosts
|
@@ -13,7 +14,8 @@ class Morpheus::Cli::Hosts
|
|
13
14
|
{:exec => :execution_request},
|
14
15
|
:wiki, :update_wiki,
|
15
16
|
:maintenance, :leave_maintenance, :placement,
|
16
|
-
:list_devices, :assign_device, :detach_device, :attach_device
|
17
|
+
:list_devices, :assign_device, :detach_device, :attach_device,
|
18
|
+
:snapshot
|
17
19
|
alias_subcommand :details, :get
|
18
20
|
set_default_subcommand :list
|
19
21
|
|
@@ -538,8 +540,9 @@ class Morpheus::Cli::Hosts
|
|
538
540
|
"Nodes" => lambda {|it| it['containers'] ? it['containers'].size : 0 },
|
539
541
|
# "Status" => lambda {|it| format_server_status(it) },
|
540
542
|
# "Power" => lambda {|it| format_server_power_state(it) },
|
541
|
-
"
|
542
|
-
"
|
543
|
+
"Managed" => lambda {|it| it['computeServerType'] ? it['computeServerType']['managed'] : ''},
|
544
|
+
"Instance" => lambda {|it| it['instance'] ? it['instance']['name'] : ''},
|
545
|
+
"Status" => lambda {|it| format_server_status_friendly(it) } # combo
|
543
546
|
}
|
544
547
|
server_columns.delete("Hostname") if server['hostname'].to_s.empty? || server['hostname'] == server['name']
|
545
548
|
server_columns.delete("IP") if server['externalIp'].to_s.empty?
|
@@ -548,6 +551,7 @@ class Morpheus::Cli::Hosts
|
|
548
551
|
server_columns.delete("Cost") if server['hourlyCost'].to_f == 0
|
549
552
|
server_columns.delete("Price") if server['hourlyPrice'].to_f == 0 || server['hourlyPrice'] == server['hourlyCost']
|
550
553
|
server_columns.delete("Labels") if server['labels'].nil? || server['labels'].empty?
|
554
|
+
server_columns.delete("Instance") if server['instance'].nil?
|
551
555
|
|
552
556
|
print_description_list(server_columns, server)
|
553
557
|
|
@@ -2043,13 +2047,18 @@ EOT
|
|
2043
2047
|
def snapshots(args)
|
2044
2048
|
options = {}
|
2045
2049
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2046
|
-
opts.banner = subcommand_usage("[host]")
|
2050
|
+
opts.banner = subcommand_usage("[host] [snapshot]")
|
2047
2051
|
# no pagination yet
|
2048
2052
|
# build_standard_list_options(opts, options)
|
2049
|
-
|
2053
|
+
build_standard_list_options(opts, options, [:details])
|
2054
|
+
opts.footer = <<-EOT
|
2055
|
+
List snapshots for a host.
|
2056
|
+
[host] is required. This is the name or id of a host.
|
2057
|
+
[snapshot] is optional. This is the name or id of a snapshot.
|
2058
|
+
EOT
|
2050
2059
|
end
|
2051
2060
|
optparse.parse!(args)
|
2052
|
-
verify_args!(args:args, optparse:optparse,
|
2061
|
+
verify_args!(args:args, optparse:optparse, min:1)
|
2053
2062
|
connect(options)
|
2054
2063
|
begin
|
2055
2064
|
server = find_host_by_name_or_id(args[0])
|
@@ -2061,25 +2070,52 @@ EOT
|
|
2061
2070
|
return
|
2062
2071
|
end
|
2063
2072
|
json_response = @servers_interface.snapshots(server['id'], params)
|
2064
|
-
snapshots = json_response['snapshots']
|
2073
|
+
snapshots = json_response['snapshots']
|
2074
|
+
# [snapshots] is done with post api filtering by id or name or externalId
|
2075
|
+
if args[1]
|
2076
|
+
if args[1] =~ /\A\d{1,}\Z/
|
2077
|
+
snapshots = snapshots.select {|it| it['id'].to_s == args[1] }
|
2078
|
+
else
|
2079
|
+
# match beginning of name of externalId
|
2080
|
+
snapshots = snapshots.select {|it| it['name'].to_s.index(args[1]) == 0 || it['externalId'].to_s.index(args[1]) == 0 }
|
2081
|
+
end
|
2082
|
+
json_response['snapshots'] = snapshots # update response for -j filtering too
|
2083
|
+
end
|
2065
2084
|
render_response(json_response, options, 'snapshots') do
|
2066
2085
|
print_h1 "Snapshots: #{server['name']}", [], options
|
2067
2086
|
if snapshots.empty?
|
2068
|
-
|
2087
|
+
if args[1]
|
2088
|
+
print cyan,"No snapshots found for '#{args[1]}'",reset,"\n"
|
2089
|
+
elsif
|
2090
|
+
print cyan,"No snapshots found",reset,"\n"
|
2091
|
+
end
|
2092
|
+
print reset, "\n"
|
2069
2093
|
else
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2073
|
-
|
2074
|
-
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2094
|
+
if options[:details]
|
2095
|
+
# this actually makes a request for each one here so don't go crazy...
|
2096
|
+
if snapshots.size > 3
|
2097
|
+
print cyan, "Showing first 3 snapshots. Use the ID to get more details.", reset, "\n"
|
2098
|
+
snapshots = snapshots.first(3)
|
2099
|
+
end
|
2100
|
+
snapshots.each do |snapshot|
|
2101
|
+
Morpheus::Cli::Snapshots.new.handle(["get", snapshot['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
2102
|
+
end
|
2103
|
+
else
|
2104
|
+
# Snapshots List
|
2105
|
+
snapshot_column_definitions = {
|
2106
|
+
"ID" => lambda {|it| it['id'] },
|
2107
|
+
"Name" => lambda {|it| it['name'] },
|
2108
|
+
"Description" => lambda {|it| it['description'] },
|
2109
|
+
# "Type" => lambda {|it| it['snapshotType'] },
|
2110
|
+
"Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
|
2111
|
+
"Status" => lambda {|it| format_snapshot_status(it) }
|
2112
|
+
}
|
2113
|
+
print cyan
|
2114
|
+
print as_pretty_table(snapshots, snapshot_column_definitions.upcase_keys!, options)
|
2115
|
+
print_results_pagination({size: snapshots.size, total: snapshots.size})
|
2116
|
+
print reset, "\n"
|
2117
|
+
end
|
2081
2118
|
end
|
2082
|
-
print reset, "\n"
|
2083
2119
|
end
|
2084
2120
|
return 0
|
2085
2121
|
rescue RestClient::Exception => e
|
@@ -2442,6 +2478,94 @@ EOT
|
|
2442
2478
|
return 0, nil
|
2443
2479
|
end
|
2444
2480
|
|
2481
|
+
def snapshot(args)
|
2482
|
+
options = {}
|
2483
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2484
|
+
opts.banner = subcommand_usage("[host]")
|
2485
|
+
opts.on( '--name VALUE', String, "Snapshot Name. Default is \"{name}.{timestamp}\"" ) do |val|
|
2486
|
+
options[:options]['name'] = val
|
2487
|
+
end
|
2488
|
+
opts.on( '--description VALUE', String, "Snapshot Description." ) do |val|
|
2489
|
+
options[:options]['description'] = val
|
2490
|
+
end
|
2491
|
+
opts.on('--memory-snapshot [on|off]', String, "Memory Snapshot? Whether to include the memory state in the snapshot.") do |val|
|
2492
|
+
options[:options]['memorySnapshot'] = val.to_s == '' || val.to_s == 'on' || val.to_s == 'true'
|
2493
|
+
end
|
2494
|
+
opts.on('--for-export [on|off]', String, "For Export? Indicates the snapshot is intended for export to storage.") do |val|
|
2495
|
+
options[:options]['forExport'] = val.to_s == '' || val.to_s == 'on' || val.to_s == 'true'
|
2496
|
+
end
|
2497
|
+
opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
|
2498
|
+
options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
2499
|
+
end
|
2500
|
+
opts.on(nil, '--no-refresh', "Do not refresh" ) do
|
2501
|
+
options[:no_refresh] = true
|
2502
|
+
end
|
2503
|
+
build_standard_add_options(opts, options, [:auto_confirm])
|
2504
|
+
opts.footer = <<-EOT
|
2505
|
+
Create a snapshot for a host.
|
2506
|
+
[host] is required. This is the name or id of a host
|
2507
|
+
EOT
|
2508
|
+
end
|
2509
|
+
optparse.parse!(args)
|
2510
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
2511
|
+
connect(options)
|
2512
|
+
server = find_host_by_name_or_id(args[0])
|
2513
|
+
payload = {}
|
2514
|
+
if options[:payload]
|
2515
|
+
payload = options[:payload]
|
2516
|
+
payload.deep_merge!({'snapshot' => parse_passed_options(options)})
|
2517
|
+
else
|
2518
|
+
payload.deep_merge!({'snapshot' => parse_passed_options(options)})
|
2519
|
+
# prompt for name and description
|
2520
|
+
name = prompt_value({'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Snapshot Name', 'description' => "Snapshot Name. Default is \"{name}.{timestamp}\""}, options)
|
2521
|
+
payload['snapshot']['name'] = name if !name.to_s.empty?
|
2522
|
+
description = prompt_value({'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'description' => "Snapshot Description."}, options)
|
2523
|
+
payload['snapshot']['description'] = description if !description.to_s.empty?
|
2524
|
+
# need to GET provision type for some settings...
|
2525
|
+
provision_type = nil
|
2526
|
+
begin
|
2527
|
+
provision_type_id = server['computeServerType']['provisionTypeId'] rescue nil
|
2528
|
+
if provision_type_id
|
2529
|
+
provision_type = @provision_types_interface.get(provision_type_id)['provisionType']
|
2530
|
+
end
|
2531
|
+
rescue => ex
|
2532
|
+
Morpheus::Logging::DarkPrinter.puts "Failed to load provision type!" if Morpheus::Logging.debug?
|
2533
|
+
end
|
2534
|
+
if provision_type && provision_type['hasMemorySnapshots']
|
2535
|
+
# prompt for memorySnapshot
|
2536
|
+
memory_snapshot = prompt_value({'fieldName' => 'memorySnapshot', 'type' => 'checkbox', 'fieldLabel' => 'Memory Snapshot?', 'description' => "Snapshot Description."}, options)
|
2537
|
+
payload['snapshot']['memorySnapshot'] = memory_snapshot if !memory_snapshot.to_s.empty?
|
2538
|
+
end
|
2539
|
+
# convert "on" and "off" to true/false
|
2540
|
+
payload.booleanize!
|
2541
|
+
end
|
2542
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to snapshot the host '#{server['name']}'?", options)
|
2543
|
+
exit 1
|
2544
|
+
end
|
2545
|
+
@servers_interface.setopts(options)
|
2546
|
+
if options[:dry_run]
|
2547
|
+
print_dry_run @servers_interface.dry.snapshot(server['id'], payload)
|
2548
|
+
return
|
2549
|
+
end
|
2550
|
+
json_response = @servers_interface.snapshot(server['id'], payload)
|
2551
|
+
render_response(json_response, options) do
|
2552
|
+
print_green_success "Snapshot initiated."
|
2553
|
+
process_id = json_response['processIds'][0] rescue nil
|
2554
|
+
if process_id
|
2555
|
+
unless options[:no_refresh]
|
2556
|
+
process = wait_for_process_execution(process_id, options)
|
2557
|
+
snapshot_id = process['resultId']
|
2558
|
+
if snapshot_id
|
2559
|
+
Morpheus::Cli::Snapshots.new.handle(["get", snapshot_id] + (options[:remote] ? ["-r",options[:remote]] : []))
|
2560
|
+
end
|
2561
|
+
end
|
2562
|
+
else
|
2563
|
+
# puts "No process returned"
|
2564
|
+
end
|
2565
|
+
end
|
2566
|
+
return 0, nil
|
2567
|
+
end
|
2568
|
+
|
2445
2569
|
## Server Devices
|
2446
2570
|
|
2447
2571
|
def list_devices(args)
|
@@ -2915,12 +2915,18 @@ class Morpheus::Cli::Instances
|
|
2915
2915
|
options = {}
|
2916
2916
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2917
2917
|
opts.banner = subcommand_usage("[instance]")
|
2918
|
-
opts.on( '--name VALUE', String, "Snapshot Name. Default is
|
2918
|
+
opts.on( '--name VALUE', String, "Snapshot Name. Default is \"{name}.{timestamp}\"" ) do |val|
|
2919
2919
|
options[:options]['name'] = val
|
2920
2920
|
end
|
2921
2921
|
opts.on( '--description VALUE', String, "Snapshot Description." ) do |val|
|
2922
2922
|
options[:options]['description'] = val
|
2923
2923
|
end
|
2924
|
+
opts.on('--memory-snapshot [on|off]', String, "Memory Snapshot? Whether to include the memory state in the snapshot.") do |val|
|
2925
|
+
options[:options]['memorySnapshot'] = val.to_s == '' || val.to_s == 'on' || val.to_s == 'true'
|
2926
|
+
end
|
2927
|
+
opts.on('--for-export [on|off]', String, "For Export? Indicates the snapshot is intended for export to storage.") do |val|
|
2928
|
+
options[:options]['forExport'] = val.to_s == '' || val.to_s == 'on' || val.to_s == 'true'
|
2929
|
+
end
|
2924
2930
|
opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
|
2925
2931
|
options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
2926
2932
|
end
|
@@ -2937,15 +2943,36 @@ EOT
|
|
2937
2943
|
verify_args!(args:args, optparse:optparse, count:1)
|
2938
2944
|
connect(options)
|
2939
2945
|
instance = find_instance_by_name_or_id(args[0])
|
2940
|
-
|
2941
|
-
exit 1
|
2942
|
-
end
|
2946
|
+
|
2943
2947
|
payload = {}
|
2944
2948
|
if options[:payload]
|
2945
2949
|
payload = options[:payload]
|
2946
2950
|
payload.deep_merge!({'snapshot' => parse_passed_options(options)})
|
2947
2951
|
else
|
2948
2952
|
payload.deep_merge!({'snapshot' => parse_passed_options(options)})
|
2953
|
+
snapshot = payload['snapshot']
|
2954
|
+
# prompt for name and description
|
2955
|
+
name = prompt_value({'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Snapshot Name', 'description' => "Snapshot Name. Default is \"{name}.{timestamp}\""}, options)
|
2956
|
+
snapshot['name'] = name if !name.to_s.empty?
|
2957
|
+
description = prompt_value({'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'description' => "Snapshot Description."}, options)
|
2958
|
+
snapshot['description'] = description if !description.to_s.empty?
|
2959
|
+
# need to GET provision type for some settings...
|
2960
|
+
provision_type = nil
|
2961
|
+
begin
|
2962
|
+
provision_type = @provision_types_interface.get(instance['layout']['provisionTypeId'])['provisionType']
|
2963
|
+
rescue => ex
|
2964
|
+
Morpheus::Logging::DarkPrinter.puts "Failed to load provision type!" if Morpheus::Logging.debug?
|
2965
|
+
end
|
2966
|
+
if provision_type && provision_type['hasMemorySnapshots']
|
2967
|
+
# prompt for memorySnapshot
|
2968
|
+
memory_snapshot = prompt_value({'fieldName' => 'memorySnapshot', 'type' => 'checkbox', 'fieldLabel' => 'Memory Snapshot?', 'description' => "Snapshot Description."}, options)
|
2969
|
+
snapshot['memorySnapshot'] = memory_snapshot if !memory_snapshot.to_s.empty?
|
2970
|
+
end
|
2971
|
+
# convert "on" and "off" to true/false
|
2972
|
+
payload.booleanize!
|
2973
|
+
end
|
2974
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to snapshot the instance '#{instance['name']}'?", options)
|
2975
|
+
exit 1
|
2949
2976
|
end
|
2950
2977
|
@instances_interface.setopts(options)
|
2951
2978
|
if options[:dry_run]
|
@@ -3470,17 +3497,18 @@ EOT
|
|
3470
3497
|
def snapshots(args)
|
3471
3498
|
options = {}
|
3472
3499
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3473
|
-
opts.banner = subcommand_usage("[instance]")
|
3500
|
+
opts.banner = subcommand_usage("[instance] [snapshot]")
|
3474
3501
|
# no pagination yet
|
3475
3502
|
# build_standard_list_options(opts, options)
|
3476
|
-
|
3503
|
+
build_standard_list_options(opts, options, [:details])
|
3477
3504
|
opts.footer = <<-EOT
|
3478
3505
|
List snapshots for an instance.
|
3479
3506
|
[instance] is required. This is the name or id of an instance
|
3507
|
+
[snapshot] is optional. this is the name or id a snapshot to filter by.
|
3480
3508
|
EOT
|
3481
3509
|
end
|
3482
3510
|
optparse.parse!(args)
|
3483
|
-
verify_args!(args:args, optparse:optparse,
|
3511
|
+
verify_args!(args:args, optparse:optparse, min:1, max: 2)
|
3484
3512
|
connect(options)
|
3485
3513
|
begin
|
3486
3514
|
instance = find_instance_by_name_or_id(args[0])
|
@@ -3491,25 +3519,52 @@ EOT
|
|
3491
3519
|
return
|
3492
3520
|
end
|
3493
3521
|
json_response = @instances_interface.snapshots(instance['id'], params)
|
3494
|
-
snapshots = json_response['snapshots']
|
3522
|
+
snapshots = json_response['snapshots']
|
3523
|
+
# [snapshots] is done with post api filtering by id or name or externalId
|
3524
|
+
if args[1]
|
3525
|
+
if args[1] =~ /\A\d{1,}\Z/
|
3526
|
+
snapshots = snapshots.select {|it| it['id'].to_s == args[1] }
|
3527
|
+
else
|
3528
|
+
# snapshots = snapshots.select {|it| it['name'] == args[1] || it['externalId'] == args[1] }
|
3529
|
+
# match beginning of name of externalId
|
3530
|
+
snapshots = snapshots.select {|it| it['name'].to_s.index(args[1]) == 0 || it['externalId'].to_s.index(args[1]) == 0 }
|
3531
|
+
end
|
3532
|
+
json_response['snapshots'] = snapshots # update response for -j filtering too
|
3533
|
+
end
|
3495
3534
|
render_response(json_response, options, 'snapshots') do
|
3496
3535
|
print_h1 "Snapshots: #{instance['name']} (#{instance['instanceType']['name']})", [], options
|
3497
3536
|
if snapshots.empty?
|
3498
|
-
|
3537
|
+
if args[1]
|
3538
|
+
print cyan,"No snapshots found for '#{args[1]}'",reset,"\n"
|
3539
|
+
elsif
|
3540
|
+
print cyan,"No snapshots found",reset,"\n"
|
3541
|
+
end
|
3542
|
+
print reset, "\n"
|
3499
3543
|
else
|
3500
|
-
|
3501
|
-
|
3502
|
-
|
3503
|
-
|
3504
|
-
|
3505
|
-
|
3506
|
-
|
3507
|
-
|
3508
|
-
|
3509
|
-
|
3510
|
-
|
3544
|
+
if options[:details]
|
3545
|
+
if snapshots.size > 3
|
3546
|
+
print cyan, "Showing first 3 snapshots. Use the ID to get more details.", reset, "\n"
|
3547
|
+
snapshots = snapshots.first(3) # this actually makes a request for each one here so don't go crazy...
|
3548
|
+
end
|
3549
|
+
snapshots.each do |snapshot|
|
3550
|
+
Morpheus::Cli::Snapshots.new.handle(["get", snapshot['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
3551
|
+
end
|
3552
|
+
else
|
3553
|
+
# Snapshots List
|
3554
|
+
snapshot_column_definitions = {
|
3555
|
+
"ID" => lambda {|it| it['id'] },
|
3556
|
+
"Name" => lambda {|it| it['name'] },
|
3557
|
+
"Description" => lambda {|it| it['description'] },
|
3558
|
+
# "Type" => lambda {|it| it['snapshotType'] },
|
3559
|
+
"Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
|
3560
|
+
"Status" => lambda {|it| format_snapshot_status(it) }
|
3561
|
+
}
|
3562
|
+
print cyan
|
3563
|
+
print as_pretty_table(snapshots, snapshot_column_definitions.upcase_keys!, options)
|
3564
|
+
print_results_pagination({size: snapshots.size, total: snapshots.size})
|
3565
|
+
print reset, "\n"
|
3566
|
+
end
|
3511
3567
|
end
|
3512
|
-
print reset, "\n"
|
3513
3568
|
end
|
3514
3569
|
return 0
|
3515
3570
|
rescue RestClient::Exception => e
|
@@ -77,11 +77,15 @@ class Morpheus::Cli::Snapshots
|
|
77
77
|
"Snapshot Type" => 'snapshotType',
|
78
78
|
"Cloud" => lambda {|it| format_name_and_id(it['zone']) },
|
79
79
|
"Datastore" => lambda {|it| format_name_and_id(it['datastore']) },
|
80
|
+
"Memory Snapshot" => lambda {|it| format_boolean(it['memorySnapshot']) },
|
81
|
+
"For Export" => lambda {|it| format_boolean(it['forExport']) },
|
80
82
|
"Parent Snapshot" => lambda {|it| format_name_and_id(it['parentSnapshot']) },
|
81
83
|
"Active" => lambda {|it| format_boolean(it['currentlyActive']) },
|
82
84
|
"Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
83
85
|
"Status" => lambda {|it| format_snapshot_status(it) }
|
84
86
|
}
|
87
|
+
description_cols.delete("Memory Snapshot") if !snapshot['memorySnapshot']
|
88
|
+
description_cols.delete("For Export") if !snapshot['forExport']
|
85
89
|
print_description_list(description_cols, snapshot)
|
86
90
|
|
87
91
|
print reset, "\n"
|
data/lib/morpheus/cli/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: morpheus-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 8.0.
|
4
|
+
version: 8.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Estes
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2025-
|
14
|
+
date: 2025-07-23 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: public_suffix
|