morpheus-cli 4.2.22 → 5.2.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/.gitignore +1 -0
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +30 -0
- data/lib/morpheus/api/billing_interface.rb +34 -0
- data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
- data/lib/morpheus/api/deploy_interface.rb +1 -1
- data/lib/morpheus/api/deployments_interface.rb +20 -1
- data/lib/morpheus/api/forgot_password_interface.rb +17 -0
- data/lib/morpheus/api/instances_interface.rb +16 -2
- data/lib/morpheus/api/rest_interface.rb +0 -6
- data/lib/morpheus/api/roles_interface.rb +14 -0
- data/lib/morpheus/api/search_interface.rb +13 -0
- data/lib/morpheus/api/servers_interface.rb +14 -0
- data/lib/morpheus/api/service_catalog_interface.rb +89 -0
- data/lib/morpheus/api/usage_interface.rb +18 -0
- data/lib/morpheus/cli.rb +7 -3
- data/lib/morpheus/cli/apps.rb +6 -27
- data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
- data/lib/morpheus/cli/backups_command.rb +3 -0
- data/lib/morpheus/cli/catalog_item_types_command.rb +622 -0
- data/lib/morpheus/cli/cli_command.rb +70 -21
- data/lib/morpheus/cli/commands/standard/curl_command.rb +3 -5
- data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
- data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
- data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
- data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
- data/lib/morpheus/cli/containers_command.rb +14 -24
- data/lib/morpheus/cli/cypher_command.rb +6 -2
- data/lib/morpheus/cli/deploy.rb +199 -90
- data/lib/morpheus/cli/deployments.rb +341 -28
- data/lib/morpheus/cli/deploys.rb +206 -41
- data/lib/morpheus/cli/error_handler.rb +7 -0
- data/lib/morpheus/cli/forgot_password.rb +133 -0
- data/lib/morpheus/cli/groups.rb +1 -1
- data/lib/morpheus/cli/health_command.rb +59 -2
- data/lib/morpheus/cli/hosts.rb +295 -35
- data/lib/morpheus/cli/instances.rb +247 -130
- data/lib/morpheus/cli/invoices_command.rb +37 -19
- data/lib/morpheus/cli/library_option_lists_command.rb +15 -7
- data/lib/morpheus/cli/library_option_types_command.rb +5 -2
- data/lib/morpheus/cli/logs_command.rb +9 -6
- data/lib/morpheus/cli/mixins/accounts_helper.rb +12 -7
- data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
- data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
- data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
- data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +108 -5
- data/lib/morpheus/cli/option_types.rb +271 -22
- data/lib/morpheus/cli/ping.rb +0 -1
- data/lib/morpheus/cli/remote.rb +35 -12
- data/lib/morpheus/cli/reports_command.rb +99 -30
- data/lib/morpheus/cli/roles.rb +453 -113
- data/lib/morpheus/cli/search_command.rb +182 -0
- data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
- data/lib/morpheus/cli/setup.rb +1 -1
- data/lib/morpheus/cli/shell.rb +33 -11
- data/lib/morpheus/cli/storage_providers_command.rb +40 -56
- data/lib/morpheus/cli/tasks.rb +29 -32
- data/lib/morpheus/cli/usage_command.rb +203 -0
- data/lib/morpheus/cli/user_settings_command.rb +1 -0
- data/lib/morpheus/cli/users.rb +12 -1
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/virtual_images.rb +429 -254
- data/lib/morpheus/cli/whoami.rb +6 -6
- data/lib/morpheus/cli/workflows.rb +33 -40
- data/lib/morpheus/formatters.rb +75 -7
- data/lib/morpheus/terminal.rb +6 -2
- metadata +14 -2
@@ -19,7 +19,7 @@ class Morpheus::Cli::Instances
|
|
19
19
|
:history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details},
|
20
20
|
:stats, :stop, :start, :restart, :actions, :action, :suspend, :eject, :stop_service, :start_service, :restart_service,
|
21
21
|
:backup, :backups, :resize, :clone, :envs, :setenv, :delenv,
|
22
|
-
:security_groups, :apply_security_groups, :run_workflow, :import_snapshot,
|
22
|
+
:security_groups, :apply_security_groups, :run_workflow, :import_snapshot, :snapshot, :snapshots,
|
23
23
|
:console, :status_check, {:containers => :list_containers},
|
24
24
|
:scaling, {:'scaling-update' => :scaling_update},
|
25
25
|
:wiki, :update_wiki,
|
@@ -80,9 +80,6 @@ class Morpheus::Cli::Instances
|
|
80
80
|
options[:owner] = val
|
81
81
|
end
|
82
82
|
opts.add_hidden_option('--created-by')
|
83
|
-
opts.on('--details', "Display more details: memory and storage usage used / max values." ) do
|
84
|
-
options[:details] = true
|
85
|
-
end
|
86
83
|
opts.on('--status STATUS', "Filter by status i.e. provisioning,running,starting,stopping") do |val|
|
87
84
|
params['status'] = (params['status'] || []) + val.to_s.split(',').collect {|s| s.strip }.select {|s| s != "" }
|
88
85
|
end
|
@@ -92,11 +89,45 @@ class Morpheus::Cli::Instances
|
|
92
89
|
opts.on('--pending-removal-only', "Only instances pending removal.") do
|
93
90
|
options[:deleted] = true
|
94
91
|
end
|
92
|
+
opts.on( '--plan NAME', String, "Filter by Plan name(s)" ) do |val|
|
93
|
+
# commas used in names a lot so use --plan one --plan two
|
94
|
+
params['plan'] ||= []
|
95
|
+
params['plan'] << val
|
96
|
+
end
|
97
|
+
opts.on( '--plan-id ID', String, "Filter by Plan id(s)" ) do |val|
|
98
|
+
params['planId'] = parse_id_list(val)
|
99
|
+
end
|
100
|
+
opts.on( '--plan-code CODE', String, "Filter by Plan code(s)" ) do |val|
|
101
|
+
params['planCode'] = parse_id_list(val)
|
102
|
+
end
|
103
|
+
opts.on('--labels label',String, "Filter by labels (keywords).") do |val|
|
104
|
+
val.split(",").each do |k|
|
105
|
+
options[:labels] ||= []
|
106
|
+
options[:labels] << k.strip
|
107
|
+
end
|
108
|
+
end
|
109
|
+
opts.on('--tags Name=Value',String, "Filter by tags (metadata name value pairs).") do |val|
|
110
|
+
val.split(",").each do |value_pair|
|
111
|
+
k,v = value_pair.strip.split("=")
|
112
|
+
options[:tags] ||= {}
|
113
|
+
options[:tags][k] ||= []
|
114
|
+
options[:tags][k] << (v || '')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
opts.on('--stats', "Display values for memory and storage usage used / max values." ) do
|
118
|
+
options[:stats] = true
|
119
|
+
end
|
120
|
+
opts.on('-a', '--details', "Display all details: plan, stats, etc" ) do
|
121
|
+
options[:details] = true
|
122
|
+
end
|
95
123
|
build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
|
96
124
|
opts.footer = "List instances."
|
97
125
|
end
|
98
126
|
optparse.parse!(args)
|
99
|
-
verify_args!(args:args,
|
127
|
+
# verify_args!(args:args, optparse:optparse, count:0)
|
128
|
+
if args.count > 0
|
129
|
+
options[:phrase] = args.join(" ")
|
130
|
+
end
|
100
131
|
connect(options)
|
101
132
|
begin
|
102
133
|
params.merge!(parse_list_options(options))
|
@@ -129,7 +160,13 @@ class Morpheus::Cli::Instances
|
|
129
160
|
|
130
161
|
params['showDeleted'] = true if options[:showDeleted]
|
131
162
|
params['deleted'] = true if options[:deleted]
|
132
|
-
|
163
|
+
params['labels'] = options[:labels] if options[:labels]
|
164
|
+
if options[:tags]
|
165
|
+
options[:tags].each do |k,v|
|
166
|
+
params['tags.' + k] = v
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
133
170
|
@instances_interface.setopts(options)
|
134
171
|
if options[:dry_run]
|
135
172
|
print_dry_run @instances_interface.dry.list(params)
|
@@ -193,7 +230,7 @@ class Morpheus::Cli::Instances
|
|
193
230
|
cpu_usage_str = !stats ? "" : generate_usage_bar((stats['usedCpu'] || stats['cpuUsage']).to_f, 100, {max_bars: 10})
|
194
231
|
memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
|
195
232
|
storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
|
196
|
-
if options[:details]
|
233
|
+
if options[:details] || options[:stats]
|
197
234
|
if stats['maxMemory'] && stats['maxMemory'].to_i != 0
|
198
235
|
memory_usage_str = memory_usage_str + cyan + format_bytes_short(stats['usedMemory']).strip.rjust(8, ' ') + " / " + format_bytes_short(stats['maxMemory']).strip
|
199
236
|
end
|
@@ -211,8 +248,9 @@ class Morpheus::Cli::Instances
|
|
211
248
|
nodes: instance['containers'].count,
|
212
249
|
status: format_instance_status(instance, cyan),
|
213
250
|
type: instance['instanceType']['name'],
|
214
|
-
group:
|
215
|
-
cloud:
|
251
|
+
group: instance['group'] ? instance['group']['name'] : nil,
|
252
|
+
cloud: instance['cloud'] ? instance['cloud']['name'] : nil,
|
253
|
+
plan: instance['plan'] ? instance['plan']['name'] : '',
|
216
254
|
version: instance['instanceVersion'] ? instance['instanceVersion'] : '',
|
217
255
|
created: format_local_dt(instance['dateCreated']),
|
218
256
|
cpu: cpu_usage_str + cyan,
|
@@ -226,12 +264,13 @@ class Morpheus::Cli::Instances
|
|
226
264
|
{:created => {:display_name => "CREATED"}},
|
227
265
|
# {:tenant => {:display_name => "TENANT"}},
|
228
266
|
{:user => {:display_name => "OWNER", :max_width => 20}},
|
267
|
+
:plan,
|
229
268
|
:nodes, {:connection => {:max_width => 30}}, :status, :cpu, :memory, :storage]
|
230
269
|
# custom pretty table columns ... this is handled in as_pretty_table now(),
|
231
270
|
# todo: remove all these.. and try to always pass rows as the json data itself..
|
232
|
-
|
233
|
-
|
234
|
-
|
271
|
+
if options[:details] != true
|
272
|
+
columns.delete(:plan)
|
273
|
+
end
|
235
274
|
print cyan
|
236
275
|
print as_pretty_table(rows, columns, options)
|
237
276
|
print reset
|
@@ -344,18 +383,16 @@ class Morpheus::Cli::Instances
|
|
344
383
|
opts.on("--environment ENV", String, "Environment code") do |val|
|
345
384
|
options[:environment] = val.to_s
|
346
385
|
end
|
347
|
-
opts.on('--
|
386
|
+
opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
|
348
387
|
options[:metadata] = val
|
349
388
|
end
|
350
|
-
opts.on('--
|
351
|
-
|
352
|
-
options[:tags] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
|
389
|
+
opts.on('--metadata LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
|
390
|
+
options[:metadata] = val
|
353
391
|
end
|
354
|
-
opts.
|
355
|
-
|
356
|
-
options[:
|
392
|
+
opts.add_hidden_option('--metadata')
|
393
|
+
opts.on('--labels LIST', String, "Labels (keywords) in the format 'foo, bar'") do |val|
|
394
|
+
options[:labels] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
|
357
395
|
end
|
358
|
-
opts.add_hidden_option('--tags')
|
359
396
|
opts.on("--copies NUMBER", Integer, "Number of copies to provision") do |val|
|
360
397
|
options[:copies] = val.to_i
|
361
398
|
end
|
@@ -422,17 +459,58 @@ class Morpheus::Cli::Instances
|
|
422
459
|
options[:instance_name] = args[0]
|
423
460
|
end
|
424
461
|
|
425
|
-
# use active group by default
|
426
|
-
options[:group] ||= @active_group_id
|
427
|
-
options[:select_datastore] = true
|
428
|
-
options[:name_required] = true
|
429
462
|
begin
|
430
463
|
payload = nil
|
431
464
|
if options[:payload]
|
432
465
|
payload = options[:payload]
|
433
466
|
# support -O OPTION switch on top of --payload
|
434
467
|
payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
|
468
|
+
# obviously should support every option that prompt supports on top of -- payload as well
|
469
|
+
# group, cloud and type for now
|
470
|
+
# todo: also support :layout, service_plan, :resource_pool, etc.
|
471
|
+
group = nil
|
472
|
+
if options[:group]
|
473
|
+
group = find_group_by_name_or_id_for_provisioning(options[:group])
|
474
|
+
if group.nil?
|
475
|
+
return 1, "group not found by #{options[:group]}"
|
476
|
+
end
|
477
|
+
#payload["siteId"] = group["id"]
|
478
|
+
payload.deep_merge!({"instance" => {"site" => {"id" => group["id"]} } })
|
479
|
+
end
|
480
|
+
if options[:cloud]
|
481
|
+
group_id = group ? group["id"] : ((payload["instance"] && payload["instance"]["site"].is_a?(Hash)) ? payload["instance"]["site"]["id"] : nil)
|
482
|
+
cloud = find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud])
|
483
|
+
if cloud.nil?
|
484
|
+
return 1, "cloud not found by #{options[:cloud]}"
|
485
|
+
end
|
486
|
+
payload["zoneId"] = cloud["id"]
|
487
|
+
payload.deep_merge!({"instance" => {"cloud" => cloud["name"] } })
|
488
|
+
end
|
489
|
+
if options[:cloud]
|
490
|
+
group_id = group ? group["id"] : ((payload["instance"] && payload["instance"]["site"].is_a?(Hash)) ? payload["instance"]["site"]["id"] : nil)
|
491
|
+
cloud = find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud])
|
492
|
+
if cloud.nil?
|
493
|
+
return 1, "cloud not found by #{options[:cloud]}"
|
494
|
+
end
|
495
|
+
payload["zoneId"] = cloud["id"]
|
496
|
+
payload.deep_merge!({"instance" => {"cloud" => cloud["name"] } })
|
497
|
+
end
|
498
|
+
if options[:instance_type_code]
|
499
|
+
# should just use find_instance_type_by_name_or_id
|
500
|
+
# note that the api actually will match name name or code
|
501
|
+
instance_type = (options[:instance_type_code].to_s =~ /\A\d{1,}\Z/) ? find_instance_type_by_id(options[:instance_type_code]) : find_instance_type_by_code(options[:instance_type_code])
|
502
|
+
if instance_type.nil?
|
503
|
+
return 1, "instance type not found by #{options[:cloud]}"
|
504
|
+
end
|
505
|
+
payload.deep_merge!({"instance" => {"type" => instance_type["code"] } })
|
506
|
+
payload.deep_merge!({"instance" => {"instanceType" => {"code" => instance_type["code"]} } })
|
507
|
+
end
|
508
|
+
|
435
509
|
else
|
510
|
+
# use active group by default
|
511
|
+
options[:group] ||= @active_group_id
|
512
|
+
options[:select_datastore] = true
|
513
|
+
options[:name_required] = true
|
436
514
|
# prompt for all the instance configuration options
|
437
515
|
# this provisioning helper method handles all (most) of the parsing and prompting
|
438
516
|
# and it relies on the method to exit non-zero on error, like a bad CLOUD or TYPE value
|
@@ -525,18 +603,18 @@ class Morpheus::Cli::Instances
|
|
525
603
|
opts.on('--group GROUP', String, "Group Name or ID") do |val|
|
526
604
|
options[:group] = val
|
527
605
|
end
|
528
|
-
opts.on('--metadata LIST', String, "Metadata tags in the format 'name:value, name:value'") do |val|
|
529
|
-
options[:metadata] = val
|
530
|
-
end
|
531
606
|
opts.on('--labels LIST', String, "Labels (keywords) in the format 'foo, bar'") do |val|
|
532
|
-
|
533
|
-
|
607
|
+
params['labels'] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
|
608
|
+
end
|
609
|
+
opts.on('--tags LIST', String, "Tags in the format 'name:value, name:value'. This will add and remove tags.") do |val|
|
610
|
+
options[:tags] = val
|
611
|
+
end
|
612
|
+
opts.on('--add-tags TAGS', String, "Add Tags in the format 'name:value, name:value'. This will only add/update tags.") do |val|
|
613
|
+
options[:add_tags] = val
|
534
614
|
end
|
535
|
-
opts.on('--tags
|
536
|
-
|
537
|
-
params['tags'] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
|
615
|
+
opts.on('--remove-tags TAGS', String, "Remove Tags in the format 'name, name:value'. This removes tags, the :value component is optional and must match if passed.") do |val|
|
616
|
+
options[:remove_tags] = val
|
538
617
|
end
|
539
|
-
opts.add_hidden_option('--tags')
|
540
618
|
opts.on('--power-schedule-type ID', String, "Power Schedule Type ID") do |val|
|
541
619
|
params['powerScheduleType'] = val == "null" ? nil : val
|
542
620
|
end
|
@@ -591,29 +669,17 @@ class Morpheus::Cli::Instances
|
|
591
669
|
payload['instance']['site'] = {'id' => group['id']}
|
592
670
|
end
|
593
671
|
# metadata tags
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
metadata = []
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
# merge IDs from current metadata
|
606
|
-
# todo: should allow quoted semicolons..
|
607
|
-
metadata_list = options[:metadata].split(",").select {|it| !it.to_s.empty? }
|
608
|
-
metadata_list = metadata_list.collect do |it|
|
609
|
-
metadata_pair = it.split(":")
|
610
|
-
row = {}
|
611
|
-
row['name'] = metadata_pair[0].to_s.strip
|
612
|
-
row['value'] = metadata_pair[1].to_s.strip
|
613
|
-
row
|
614
|
-
end
|
615
|
-
payload['instance']['metadata'] = metadata_list
|
616
|
-
end
|
672
|
+
if options[:tags]
|
673
|
+
# api version 4.2.5 and later supports tags, older versions expect metadata
|
674
|
+
# todo: use tags instead like everywhere else
|
675
|
+
# payload['instance']['tags'] = parse_metadata(options[:tags])
|
676
|
+
payload['instance']['metadata'] = parse_metadata(options[:tags])
|
677
|
+
end
|
678
|
+
if options[:add_tags]
|
679
|
+
payload['instance']['addTags'] = parse_metadata(options[:add_tags])
|
680
|
+
end
|
681
|
+
if options[:remove_tags]
|
682
|
+
payload['instance']['removeTags'] = parse_metadata(options[:remove_tags])
|
617
683
|
end
|
618
684
|
if payload['instance'].empty? && params.empty? && options[:owner].nil?
|
619
685
|
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
@@ -1148,6 +1214,9 @@ class Morpheus::Cli::Instances
|
|
1148
1214
|
opts.on( nil, '--scaling', "Display Instance Scaling Settings" ) do
|
1149
1215
|
options[:include_scaling] = true
|
1150
1216
|
end
|
1217
|
+
opts.on( nil, '--costs', "Display Cost and Price" ) do
|
1218
|
+
options[:include_costs] = true
|
1219
|
+
end
|
1151
1220
|
opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
|
1152
1221
|
options[:refresh_until_status] ||= "running,failed"
|
1153
1222
|
if !val.to_s.empty?
|
@@ -1211,7 +1280,18 @@ class Morpheus::Cli::Instances
|
|
1211
1280
|
instance = json_response['instance']
|
1212
1281
|
stats = instance['stats'] || json_response['stats'] || {}
|
1213
1282
|
# load_balancers = json_response['loadBalancers'] || {}
|
1214
|
-
|
1283
|
+
# metadata tags used to be returned as metadata and are now returned as tags
|
1284
|
+
# the problem is tags is what we used to call Labels (keywords)
|
1285
|
+
# the api will change to tags and labels, so handle the old format as long as metadata is returned.
|
1286
|
+
labels = nil
|
1287
|
+
tags = nil
|
1288
|
+
if instance.key?('labels')
|
1289
|
+
labels = instance['labels']
|
1290
|
+
tags = instance['tags']
|
1291
|
+
else
|
1292
|
+
labels = instance['tags']
|
1293
|
+
tags = instance['metadata']
|
1294
|
+
end
|
1215
1295
|
# containers are fetched via separate api call
|
1216
1296
|
containers = nil
|
1217
1297
|
if options[:include_containers]
|
@@ -1249,9 +1329,11 @@ class Morpheus::Cli::Instances
|
|
1249
1329
|
"Layout" => lambda {|it| it['layout'] ? it['layout']['name'] : '' },
|
1250
1330
|
"Version" => lambda {|it| it['instanceVersion'] },
|
1251
1331
|
"Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
|
1332
|
+
# "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
|
1333
|
+
# "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
|
1252
1334
|
"Environment" => 'instanceContext',
|
1253
|
-
"Labels" => lambda {|it|
|
1254
|
-
"
|
1335
|
+
"Labels" => lambda {|it| labels ? labels.join(',') : '' },
|
1336
|
+
"Tags" => lambda {|it| tags ? tags.collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
1255
1337
|
"Owner" => lambda {|it|
|
1256
1338
|
if it['owner']
|
1257
1339
|
(it['owner']['username'] || it['owner']['id'])
|
@@ -1270,11 +1352,14 @@ class Morpheus::Cli::Instances
|
|
1270
1352
|
"Connection" => lambda {|it| format_instance_connection_string(it) },
|
1271
1353
|
"Status" => lambda {|it| format_instance_status(it) }
|
1272
1354
|
}
|
1355
|
+
description_cols.delete("Labels") if labels.nil? || labels.empty?
|
1356
|
+
description_cols.delete("Tags") if tags.nil? || tags.empty?
|
1273
1357
|
description_cols.delete("Power Schedule") if instance['powerSchedule'].nil?
|
1274
1358
|
description_cols.delete("Expire Date") if instance['expireDate'].nil?
|
1275
1359
|
description_cols.delete("Shutdown Date") if instance['shutdownDate'].nil?
|
1276
1360
|
description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if instance['status'] == 'pendingRemoval'
|
1277
1361
|
description_cols.delete("Last Deployment") if instance['lastDeploy'].nil?
|
1362
|
+
#description_cols.delete("Environment") if instance['instanceContext'].nil?
|
1278
1363
|
print_description_list(description_cols, instance)
|
1279
1364
|
|
1280
1365
|
if instance['statusMessage']
|
@@ -1304,6 +1389,16 @@ class Morpheus::Cli::Instances
|
|
1304
1389
|
print_h2 "Instance Usage", options
|
1305
1390
|
print_stats_usage(stats)
|
1306
1391
|
end
|
1392
|
+
|
1393
|
+
if options[:include_costs]
|
1394
|
+
print_h2 "Instance Cost"
|
1395
|
+
cost_columns = {
|
1396
|
+
"Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
|
1397
|
+
"Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
|
1398
|
+
}
|
1399
|
+
print_description_list(cost_columns, instance)
|
1400
|
+
end
|
1401
|
+
|
1307
1402
|
print reset, "\n"
|
1308
1403
|
|
1309
1404
|
# if options[:include_lb]
|
@@ -2578,6 +2673,48 @@ class Morpheus::Cli::Instances
|
|
2578
2673
|
end
|
2579
2674
|
end
|
2580
2675
|
|
2676
|
+
def snapshot(args)
|
2677
|
+
options = {}
|
2678
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2679
|
+
opts.banner = subcommand_usage("[instance]")
|
2680
|
+
opts.on( '--name VALUE', String, "Snapshot Name. Default is server name + timestamp" ) do |val|
|
2681
|
+
options[:options]['name'] = val
|
2682
|
+
end
|
2683
|
+
opts.on( '--description VALUE', String, "Snapshot Description." ) do |val|
|
2684
|
+
options[:options]['description'] = val
|
2685
|
+
end
|
2686
|
+
build_standard_add_options(opts, options, [:auto_confirm])
|
2687
|
+
opts.footer = <<-EOT
|
2688
|
+
Create a snapshot for an instance.
|
2689
|
+
[instance] is required. This is the name or id of an instance
|
2690
|
+
EOT
|
2691
|
+
end
|
2692
|
+
optparse.parse!(args)
|
2693
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
2694
|
+
connect(options)
|
2695
|
+
instance = find_instance_by_name_or_id(args[0])
|
2696
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to snapshot the instance '#{instance['name']}'?", options)
|
2697
|
+
exit 1
|
2698
|
+
end
|
2699
|
+
payload = {}
|
2700
|
+
if options[:payload]
|
2701
|
+
payload = options[:payload]
|
2702
|
+
payload.deep_merge!({'snapshot' => parse_passed_options(options)})
|
2703
|
+
else
|
2704
|
+
payload.deep_merge!({'snapshot' => parse_passed_options(options)})
|
2705
|
+
end
|
2706
|
+
@instances_interface.setopts(options)
|
2707
|
+
if options[:dry_run]
|
2708
|
+
print_dry_run @instances_interface.dry.snapshot(instance['id'], payload)
|
2709
|
+
return
|
2710
|
+
end
|
2711
|
+
json_response = @instances_interface.snapshot(instance['id'], payload)
|
2712
|
+
render_response(json_response, options, 'snapshots') do
|
2713
|
+
print_green_success "Snapshot initiated."
|
2714
|
+
end
|
2715
|
+
return 0, nil
|
2716
|
+
end
|
2717
|
+
|
2581
2718
|
def remove(args)
|
2582
2719
|
options = {}
|
2583
2720
|
query_params = {}
|
@@ -2880,6 +3017,57 @@ class Morpheus::Cli::Instances
|
|
2880
3017
|
end
|
2881
3018
|
end
|
2882
3019
|
|
3020
|
+
def snapshots(args)
|
3021
|
+
options = {}
|
3022
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3023
|
+
opts.banner = subcommand_usage("[instance]")
|
3024
|
+
# no pagination yet
|
3025
|
+
# build_standard_list_options(opts, options)
|
3026
|
+
build_standard_get_options(opts, options)
|
3027
|
+
opts.footer = <<-EOT
|
3028
|
+
List snapshots for an instance.
|
3029
|
+
[instance] is required. This is the name or id of an instance
|
3030
|
+
EOT
|
3031
|
+
end
|
3032
|
+
optparse.parse!(args)
|
3033
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
3034
|
+
connect(options)
|
3035
|
+
begin
|
3036
|
+
instance = find_instance_by_name_or_id(args[0])
|
3037
|
+
params = {}
|
3038
|
+
@instances_interface.setopts(options)
|
3039
|
+
if options[:dry_run]
|
3040
|
+
print_dry_run @instances_interface.dry.snapshots(instance['id'], params)
|
3041
|
+
return
|
3042
|
+
end
|
3043
|
+
json_response = @instances_interface.snapshots(instance['id'], params)
|
3044
|
+
snapshots = json_response['snapshots']
|
3045
|
+
render_response(json_response, options, 'snapshots') do
|
3046
|
+
print_h1 "Snapshots: #{instance['name']} (#{instance['instanceType']['name']})", [], options
|
3047
|
+
if snapshots.empty?
|
3048
|
+
print cyan,"No snapshots found",reset,"\n"
|
3049
|
+
else
|
3050
|
+
snapshot_column_definitions = {
|
3051
|
+
"ID" => lambda {|it| it['id'] },
|
3052
|
+
"Name" => lambda {|it| it['name'] },
|
3053
|
+
"Description" => lambda {|it| it['description'] },
|
3054
|
+
# "Type" => lambda {|it| it['snapshotType'] },
|
3055
|
+
"Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
|
3056
|
+
"Status" => lambda {|it| format_snapshot_status(it) }
|
3057
|
+
}
|
3058
|
+
print cyan
|
3059
|
+
print as_pretty_table(snapshots, snapshot_column_definitions.upcase_keys!, options)
|
3060
|
+
print_results_pagination({size: snapshots.size, total: snapshots.size})
|
3061
|
+
end
|
3062
|
+
print reset, "\n"
|
3063
|
+
end
|
3064
|
+
return 0
|
3065
|
+
rescue RestClient::Exception => e
|
3066
|
+
print_rest_exception(e, options)
|
3067
|
+
exit 1
|
3068
|
+
end
|
3069
|
+
end
|
3070
|
+
|
2883
3071
|
def import_snapshot(args)
|
2884
3072
|
options = {}
|
2885
3073
|
query_params = {}
|
@@ -3792,51 +3980,6 @@ private
|
|
3792
3980
|
end
|
3793
3981
|
end
|
3794
3982
|
|
3795
|
-
def format_instance_status(instance, return_color=cyan)
|
3796
|
-
out = ""
|
3797
|
-
status_string = instance['status'].to_s
|
3798
|
-
if status_string == 'running'
|
3799
|
-
out << "#{green}#{status_string.upcase}#{return_color}"
|
3800
|
-
elsif status_string == 'provisioning'
|
3801
|
-
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
3802
|
-
elsif status_string == 'stopped' or status_string == 'failed'
|
3803
|
-
out << "#{red}#{status_string.upcase}#{return_color}"
|
3804
|
-
else
|
3805
|
-
out << "#{yellow}#{status_string.upcase}#{return_color}"
|
3806
|
-
end
|
3807
|
-
out
|
3808
|
-
end
|
3809
|
-
|
3810
|
-
def format_instance_connection_string(instance)
|
3811
|
-
if !instance['connectionInfo'].nil? && instance['connectionInfo'].empty? == false
|
3812
|
-
connection_string = "#{instance['connectionInfo'][0]['ip']}:#{instance['connectionInfo'][0]['port']}"
|
3813
|
-
end
|
3814
|
-
end
|
3815
|
-
|
3816
|
-
def format_container_status(container, return_color=cyan)
|
3817
|
-
out = ""
|
3818
|
-
status_string = container['status'].to_s
|
3819
|
-
if status_string == 'running'
|
3820
|
-
out << "#{green}#{status_string.upcase}#{return_color}"
|
3821
|
-
elsif status_string == 'provisioning'
|
3822
|
-
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
3823
|
-
elsif status_string == 'stopped' or status_string == 'failed'
|
3824
|
-
out << "#{red}#{status_string.upcase}#{return_color}"
|
3825
|
-
else
|
3826
|
-
out << "#{yellow}#{status_string.upcase}#{return_color}"
|
3827
|
-
end
|
3828
|
-
out
|
3829
|
-
end
|
3830
|
-
|
3831
|
-
def format_container_connection_string(container)
|
3832
|
-
if !container['ports'].nil? && container['ports'].empty? == false
|
3833
|
-
connection_string = "#{container['ip']}:#{container['ports'][0]['external']}"
|
3834
|
-
else
|
3835
|
-
# eh? more logic needed here i think, see taglib morph:containerLocationMenu
|
3836
|
-
connection_string = "#{container['ip']}"
|
3837
|
-
end
|
3838
|
-
end
|
3839
|
-
|
3840
3983
|
def instance_scaling_option_types(instance=nil)
|
3841
3984
|
|
3842
3985
|
# Group
|
@@ -3890,17 +4033,6 @@ private
|
|
3890
4033
|
list
|
3891
4034
|
end
|
3892
4035
|
|
3893
|
-
def format_instance_container_display_name(instance, plural=false)
|
3894
|
-
#<span class="info-label">${[null,'docker'].contains(instance.layout?.provisionType?.code) ? 'Containers' : 'Virtual Machines'}:</span> <span class="info-value">${instance.containers?.size()}</span>
|
3895
|
-
v = plural ? "Containers" : "Container"
|
3896
|
-
if instance && instance['layout'] && instance['layout'].key?("provisionTypeCode")
|
3897
|
-
if [nil, 'docker'].include?(instance['layout']["provisionTypeCode"])
|
3898
|
-
v = plural ? "Virtual Machines" : "Virtual Machine"
|
3899
|
-
end
|
3900
|
-
end
|
3901
|
-
return v
|
3902
|
-
end
|
3903
|
-
|
3904
4036
|
def print_instance_threshold_description_list(instance_threshold)
|
3905
4037
|
description_cols = {
|
3906
4038
|
# "Instance" => lambda {|it| "#{instance['id']} - #{instance['name']}" },
|
@@ -4006,19 +4138,4 @@ private
|
|
4006
4138
|
}
|
4007
4139
|
end
|
4008
4140
|
|
4009
|
-
def format_app_deploy_status(status, return_color=cyan)
|
4010
|
-
out = ""
|
4011
|
-
s = status.to_s.downcase
|
4012
|
-
if s == 'deployed'
|
4013
|
-
out << "#{green}#{s.upcase}#{return_color}"
|
4014
|
-
elsif s == 'open' || s == 'archived' || s == 'committed'
|
4015
|
-
out << "#{cyan}#{s.upcase}#{return_color}"
|
4016
|
-
elsif s == 'failed'
|
4017
|
-
out << "#{red}#{s.upcase}#{return_color}"
|
4018
|
-
else
|
4019
|
-
out << "#{yellow}#{s.upcase}#{return_color}"
|
4020
|
-
end
|
4021
|
-
out
|
4022
|
-
end
|
4023
|
-
|
4024
4141
|
end
|