morpheus-cli 5.2.0 → 5.2.4.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/README.md +3 -3
- data/lib/morpheus/api/instances_interface.rb +21 -0
- data/lib/morpheus/api/invoices_interface.rb +12 -3
- data/lib/morpheus/cli/backup_jobs_command.rb +1 -1
- data/lib/morpheus/cli/backups_command.rb +2 -3
- data/lib/morpheus/cli/budgets_command.rb +389 -319
- data/lib/morpheus/cli/cli_command.rb +19 -9
- data/lib/morpheus/cli/commands/standard/curl_command.rb +25 -10
- data/lib/morpheus/cli/commands/standard/history_command.rb +6 -2
- data/lib/morpheus/cli/dashboard_command.rb +260 -20
- data/lib/morpheus/cli/hosts.rb +43 -2
- data/lib/morpheus/cli/instances.rb +189 -44
- data/lib/morpheus/cli/invoices_command.rb +73 -55
- data/lib/morpheus/cli/jobs_command.rb +94 -92
- data/lib/morpheus/cli/library_option_types_command.rb +5 -3
- data/lib/morpheus/cli/mixins/print_helper.rb +13 -6
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +12 -5
- data/lib/morpheus/cli/projects_command.rb +1 -1
- data/lib/morpheus/cli/user_sources_command.rb +118 -134
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/virtual_images.rb +1 -1
- data/lib/morpheus/formatters.rb +21 -11
- metadata +2 -2
|
@@ -19,6 +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
|
+
:lock, :unlock, :clone_image,
|
|
22
23
|
:security_groups, :apply_security_groups, :run_workflow, :import_snapshot, :snapshot, :snapshots,
|
|
23
24
|
:console, :status_check, {:containers => :list_containers},
|
|
24
25
|
:scaling, {:'scaling-update' => :scaling_update},
|
|
@@ -459,17 +460,58 @@ class Morpheus::Cli::Instances
|
|
|
459
460
|
options[:instance_name] = args[0]
|
|
460
461
|
end
|
|
461
462
|
|
|
462
|
-
# use active group by default
|
|
463
|
-
options[:group] ||= @active_group_id
|
|
464
|
-
options[:select_datastore] = true
|
|
465
|
-
options[:name_required] = true
|
|
466
463
|
begin
|
|
467
464
|
payload = nil
|
|
468
465
|
if options[:payload]
|
|
469
466
|
payload = options[:payload]
|
|
470
467
|
# support -O OPTION switch on top of --payload
|
|
471
468
|
payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
|
|
469
|
+
# obviously should support every option that prompt supports on top of -- payload as well
|
|
470
|
+
# group, cloud and type for now
|
|
471
|
+
# todo: also support :layout, service_plan, :resource_pool, etc.
|
|
472
|
+
group = nil
|
|
473
|
+
if options[:group]
|
|
474
|
+
group = find_group_by_name_or_id_for_provisioning(options[:group])
|
|
475
|
+
if group.nil?
|
|
476
|
+
return 1, "group not found by #{options[:group]}"
|
|
477
|
+
end
|
|
478
|
+
#payload["siteId"] = group["id"]
|
|
479
|
+
payload.deep_merge!({"instance" => {"site" => {"id" => group["id"]} } })
|
|
480
|
+
end
|
|
481
|
+
if options[:cloud]
|
|
482
|
+
group_id = group ? group["id"] : ((payload["instance"] && payload["instance"]["site"].is_a?(Hash)) ? payload["instance"]["site"]["id"] : nil)
|
|
483
|
+
cloud = find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud])
|
|
484
|
+
if cloud.nil?
|
|
485
|
+
return 1, "cloud not found by #{options[:cloud]}"
|
|
486
|
+
end
|
|
487
|
+
payload["zoneId"] = cloud["id"]
|
|
488
|
+
payload.deep_merge!({"instance" => {"cloud" => cloud["name"] } })
|
|
489
|
+
end
|
|
490
|
+
if options[:cloud]
|
|
491
|
+
group_id = group ? group["id"] : ((payload["instance"] && payload["instance"]["site"].is_a?(Hash)) ? payload["instance"]["site"]["id"] : nil)
|
|
492
|
+
cloud = find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud])
|
|
493
|
+
if cloud.nil?
|
|
494
|
+
return 1, "cloud not found by #{options[:cloud]}"
|
|
495
|
+
end
|
|
496
|
+
payload["zoneId"] = cloud["id"]
|
|
497
|
+
payload.deep_merge!({"instance" => {"cloud" => cloud["name"] } })
|
|
498
|
+
end
|
|
499
|
+
if options[:instance_type_code]
|
|
500
|
+
# should just use find_instance_type_by_name_or_id
|
|
501
|
+
# note that the api actually will match name name or code
|
|
502
|
+
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])
|
|
503
|
+
if instance_type.nil?
|
|
504
|
+
return 1, "instance type not found by #{options[:cloud]}"
|
|
505
|
+
end
|
|
506
|
+
payload.deep_merge!({"instance" => {"type" => instance_type["code"] } })
|
|
507
|
+
payload.deep_merge!({"instance" => {"instanceType" => {"code" => instance_type["code"]} } })
|
|
508
|
+
end
|
|
509
|
+
|
|
472
510
|
else
|
|
511
|
+
# use active group by default
|
|
512
|
+
options[:group] ||= @active_group_id
|
|
513
|
+
options[:select_datastore] = true
|
|
514
|
+
options[:name_required] = true
|
|
473
515
|
# prompt for all the instance configuration options
|
|
474
516
|
# this provisioning helper method handles all (most) of the parsing and prompting
|
|
475
517
|
# and it relies on the method to exit non-zero on error, like a bad CLOUD or TYPE value
|
|
@@ -562,16 +604,17 @@ class Morpheus::Cli::Instances
|
|
|
562
604
|
opts.on('--group GROUP', String, "Group Name or ID") do |val|
|
|
563
605
|
options[:group] = val
|
|
564
606
|
end
|
|
565
|
-
opts.on('--
|
|
566
|
-
|
|
607
|
+
opts.on('--labels [LIST]', String, "Labels (keywords) in the format 'foo, bar'") do |val|
|
|
608
|
+
params['labels'] = val.to_s.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
|
|
567
609
|
end
|
|
568
|
-
opts.on('--
|
|
569
|
-
options[:
|
|
610
|
+
opts.on('--tags LIST', String, "Tags in the format 'name:value, name:value'. This will add and remove tags.") do |val|
|
|
611
|
+
options[:tags] = val
|
|
570
612
|
end
|
|
571
|
-
opts.
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
613
|
+
opts.on('--add-tags TAGS', String, "Add Tags in the format 'name:value, name:value'. This will only add/update tags.") do |val|
|
|
614
|
+
options[:add_tags] = val
|
|
615
|
+
end
|
|
616
|
+
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|
|
|
617
|
+
options[:remove_tags] = val
|
|
575
618
|
end
|
|
576
619
|
opts.on('--power-schedule-type ID', String, "Power Schedule Type ID") do |val|
|
|
577
620
|
params['powerScheduleType'] = val == "null" ? nil : val
|
|
@@ -627,32 +670,17 @@ class Morpheus::Cli::Instances
|
|
|
627
670
|
payload['instance']['site'] = {'id' => group['id']}
|
|
628
671
|
end
|
|
629
672
|
# metadata tags
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
metadata = []
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
# merge IDs from current metadata
|
|
642
|
-
# todo: should allow quoted semicolons..
|
|
643
|
-
metadata_list = options[:metadata].split(",").select {|it| !it.to_s.empty? }
|
|
644
|
-
metadata_list = metadata_list.collect do |it|
|
|
645
|
-
metadata_pair = it.split(":")
|
|
646
|
-
if metadata_pair.size == 1 && it.include?("=")
|
|
647
|
-
metadata_pair = it.split("=")
|
|
648
|
-
end
|
|
649
|
-
row = {}
|
|
650
|
-
row['name'] = metadata_pair[0].to_s.strip
|
|
651
|
-
row['value'] = metadata_pair[1].to_s.strip
|
|
652
|
-
row
|
|
653
|
-
end
|
|
654
|
-
payload['instance']['metadata'] = metadata_list
|
|
655
|
-
end
|
|
673
|
+
if options[:tags]
|
|
674
|
+
# api version 4.2.5 and later supports tags, older versions expect metadata
|
|
675
|
+
# todo: use tags instead like everywhere else
|
|
676
|
+
# payload['instance']['tags'] = parse_metadata(options[:tags])
|
|
677
|
+
payload['instance']['metadata'] = parse_metadata(options[:tags])
|
|
678
|
+
end
|
|
679
|
+
if options[:add_tags]
|
|
680
|
+
payload['instance']['addTags'] = parse_metadata(options[:add_tags])
|
|
681
|
+
end
|
|
682
|
+
if options[:remove_tags]
|
|
683
|
+
payload['instance']['removeTags'] = parse_metadata(options[:remove_tags])
|
|
656
684
|
end
|
|
657
685
|
if payload['instance'].empty? && params.empty? && options[:owner].nil?
|
|
658
686
|
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
|
@@ -1256,11 +1284,14 @@ class Morpheus::Cli::Instances
|
|
|
1256
1284
|
# metadata tags used to be returned as metadata and are now returned as tags
|
|
1257
1285
|
# the problem is tags is what we used to call Labels (keywords)
|
|
1258
1286
|
# the api will change to tags and labels, so handle the old format as long as metadata is returned.
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1287
|
+
labels = nil
|
|
1288
|
+
tags = nil
|
|
1289
|
+
if instance.key?('labels')
|
|
1290
|
+
labels = instance['labels']
|
|
1291
|
+
tags = instance['tags']
|
|
1262
1292
|
else
|
|
1263
|
-
|
|
1293
|
+
labels = instance['tags']
|
|
1294
|
+
tags = instance['metadata']
|
|
1264
1295
|
end
|
|
1265
1296
|
# containers are fetched via separate api call
|
|
1266
1297
|
containers = nil
|
|
@@ -1302,7 +1333,7 @@ class Morpheus::Cli::Instances
|
|
|
1302
1333
|
# "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
|
|
1303
1334
|
# "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
|
|
1304
1335
|
"Environment" => 'instanceContext',
|
|
1305
|
-
"Labels" => lambda {|it|
|
|
1336
|
+
"Labels" => lambda {|it| labels ? labels.join(',') : '' },
|
|
1306
1337
|
"Tags" => lambda {|it| tags ? tags.collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
|
1307
1338
|
"Owner" => lambda {|it|
|
|
1308
1339
|
if it['owner']
|
|
@@ -1320,6 +1351,7 @@ class Morpheus::Cli::Instances
|
|
|
1320
1351
|
"Shutdown Date" => lambda {|it| it['shutdownDate'] ? format_local_dt(it['shutdownDate']) : '' },
|
|
1321
1352
|
"Nodes" => lambda {|it| it['containers'] ? it['containers'].count : 0 },
|
|
1322
1353
|
"Connection" => lambda {|it| format_instance_connection_string(it) },
|
|
1354
|
+
"Locked" => lambda {|it| format_boolean(it['locked']) },
|
|
1323
1355
|
"Status" => lambda {|it| format_instance_status(it) }
|
|
1324
1356
|
}
|
|
1325
1357
|
description_cols.delete("Labels") if labels.nil? || labels.empty?
|
|
@@ -1329,6 +1361,7 @@ class Morpheus::Cli::Instances
|
|
|
1329
1361
|
description_cols.delete("Shutdown Date") if instance['shutdownDate'].nil?
|
|
1330
1362
|
description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if instance['status'] == 'pendingRemoval'
|
|
1331
1363
|
description_cols.delete("Last Deployment") if instance['lastDeploy'].nil?
|
|
1364
|
+
description_cols.delete("Locked") if instance['locked'] != true
|
|
1332
1365
|
#description_cols.delete("Environment") if instance['instanceContext'].nil?
|
|
1333
1366
|
print_description_list(description_cols, instance)
|
|
1334
1367
|
|
|
@@ -3020,7 +3053,8 @@ EOT
|
|
|
3020
3053
|
snapshot_column_definitions = {
|
|
3021
3054
|
"ID" => lambda {|it| it['id'] },
|
|
3022
3055
|
"Name" => lambda {|it| it['name'] },
|
|
3023
|
-
"Description" => lambda {|it| it['
|
|
3056
|
+
"Description" => lambda {|it| it['description'] },
|
|
3057
|
+
# "Type" => lambda {|it| it['snapshotType'] },
|
|
3024
3058
|
"Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
|
|
3025
3059
|
"Status" => lambda {|it| format_snapshot_status(it) }
|
|
3026
3060
|
}
|
|
@@ -3857,6 +3891,117 @@ EOT
|
|
|
3857
3891
|
return 0
|
|
3858
3892
|
end
|
|
3859
3893
|
|
|
3894
|
+
def clone_image(args)
|
|
3895
|
+
options = {}
|
|
3896
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
3897
|
+
opts.banner = subcommand_usage("[instance]")
|
|
3898
|
+
opts.on( '--name VALUE', String, "Image Name (Template Name). Default is server name + timestamp" ) do |val|
|
|
3899
|
+
options[:options]['templateName'] = val
|
|
3900
|
+
end
|
|
3901
|
+
build_standard_update_options(opts, options)
|
|
3902
|
+
opts.footer = <<-EOT
|
|
3903
|
+
Clone to image (template) for an instance
|
|
3904
|
+
[instance] is required. This is the name or id of an instance
|
|
3905
|
+
EOT
|
|
3906
|
+
end
|
|
3907
|
+
optparse.parse!(args)
|
|
3908
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
3909
|
+
connect(options)
|
|
3910
|
+
instance = find_instance_by_name_or_id(args[0])
|
|
3911
|
+
return 1 if instance.nil?
|
|
3912
|
+
payload = {}
|
|
3913
|
+
if options[:payload]
|
|
3914
|
+
payload = options[:payload]
|
|
3915
|
+
payload.deep_merge!(parse_passed_options(options))
|
|
3916
|
+
else
|
|
3917
|
+
payload.deep_merge!(parse_passed_options(options))
|
|
3918
|
+
if payload['templateName'].nil?
|
|
3919
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'templateName', 'type' => 'text', 'fieldLabel' => 'Image Name', 'description' => 'Choose a name for the new image template. Default is the server name + timestamp'}], options[:options])
|
|
3920
|
+
if v_prompt['templateName'].to_s != ''
|
|
3921
|
+
payload['templateName'] = v_prompt['templateName']
|
|
3922
|
+
end
|
|
3923
|
+
end
|
|
3924
|
+
end
|
|
3925
|
+
@instances_interface.setopts(options)
|
|
3926
|
+
if options[:dry_run]
|
|
3927
|
+
print_dry_run @instances_interface.dry.clone_image(instance['id'], payload)
|
|
3928
|
+
return
|
|
3929
|
+
end
|
|
3930
|
+
json_response = @instances_interface.clone_image(instance['id'], payload)
|
|
3931
|
+
render_response(json_response, options) do
|
|
3932
|
+
print_green_success "Clone Image initiated."
|
|
3933
|
+
end
|
|
3934
|
+
return 0, nil
|
|
3935
|
+
end
|
|
3936
|
+
|
|
3937
|
+
def lock(args)
|
|
3938
|
+
options = {}
|
|
3939
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
3940
|
+
opts.banner = subcommand_usage("[instance]")
|
|
3941
|
+
build_standard_update_options(opts, options)
|
|
3942
|
+
opts.footer = <<-EOT
|
|
3943
|
+
Lock an instance
|
|
3944
|
+
[instance] is required. This is the name or id of an instance
|
|
3945
|
+
EOT
|
|
3946
|
+
end
|
|
3947
|
+
optparse.parse!(args)
|
|
3948
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
3949
|
+
connect(options)
|
|
3950
|
+
instance = find_instance_by_name_or_id(args[0])
|
|
3951
|
+
return 1 if instance.nil?
|
|
3952
|
+
payload = {}
|
|
3953
|
+
if options[:payload]
|
|
3954
|
+
payload = options[:payload]
|
|
3955
|
+
payload.deep_merge!(parse_passed_options(options))
|
|
3956
|
+
else
|
|
3957
|
+
payload.deep_merge!(parse_passed_options(options))
|
|
3958
|
+
end
|
|
3959
|
+
@instances_interface.setopts(options)
|
|
3960
|
+
if options[:dry_run]
|
|
3961
|
+
print_dry_run @instances_interface.dry.lock(instance['id'], payload)
|
|
3962
|
+
return
|
|
3963
|
+
end
|
|
3964
|
+
json_response = @instances_interface.lock(instance['id'], payload)
|
|
3965
|
+
render_response(json_response, options) do
|
|
3966
|
+
print_green_success "Locked instance #{instance['name']}"
|
|
3967
|
+
end
|
|
3968
|
+
return 0, nil
|
|
3969
|
+
end
|
|
3970
|
+
|
|
3971
|
+
def unlock(args)
|
|
3972
|
+
options = {}
|
|
3973
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
3974
|
+
opts.banner = subcommand_usage("[instance]")
|
|
3975
|
+
build_standard_update_options(opts, options)
|
|
3976
|
+
opts.footer = <<-EOT
|
|
3977
|
+
Unlock an instance
|
|
3978
|
+
[instance] is required. This is the name or id of an instance
|
|
3979
|
+
EOT
|
|
3980
|
+
end
|
|
3981
|
+
optparse.parse!(args)
|
|
3982
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
3983
|
+
connect(options)
|
|
3984
|
+
instance = find_instance_by_name_or_id(args[0])
|
|
3985
|
+
return 1 if instance.nil?
|
|
3986
|
+
payload = {}
|
|
3987
|
+
if options[:payload]
|
|
3988
|
+
payload = options[:payload]
|
|
3989
|
+
payload.deep_merge!(parse_passed_options(options))
|
|
3990
|
+
else
|
|
3991
|
+
payload.deep_merge!(parse_passed_options(options))
|
|
3992
|
+
end
|
|
3993
|
+
@instances_interface.setopts(options)
|
|
3994
|
+
if options[:dry_run]
|
|
3995
|
+
print_dry_run @instances_interface.dry.unlock(instance['id'], payload)
|
|
3996
|
+
return
|
|
3997
|
+
end
|
|
3998
|
+
json_response = @instances_interface.unlock(instance['id'], payload)
|
|
3999
|
+
render_response(json_response, options) do
|
|
4000
|
+
print_green_success "Unlocked instance #{instance['name']}"
|
|
4001
|
+
end
|
|
4002
|
+
return 0, nil
|
|
4003
|
+
end
|
|
4004
|
+
|
|
3860
4005
|
private
|
|
3861
4006
|
|
|
3862
4007
|
def find_zone_by_name_or_id(group_id, val)
|
|
@@ -8,7 +8,7 @@ class Morpheus::Cli::InvoicesCommand
|
|
|
8
8
|
|
|
9
9
|
set_command_name :'invoices'
|
|
10
10
|
|
|
11
|
-
register_subcommands :list, :get, :refresh,
|
|
11
|
+
register_subcommands :list, :get, :update, :refresh,
|
|
12
12
|
:list_line_items, :get_line_item
|
|
13
13
|
|
|
14
14
|
def connect(opts)
|
|
@@ -33,7 +33,6 @@ class Morpheus::Cli::InvoicesCommand
|
|
|
33
33
|
options[:show_estimates] = true
|
|
34
34
|
# options[:show_costs] = true
|
|
35
35
|
options[:show_prices] = true
|
|
36
|
-
# options[:show_raw_data] = true
|
|
37
36
|
end
|
|
38
37
|
opts.on('--dates', "Display Ref Start, Ref End, etc.") do |val|
|
|
39
38
|
options[:show_dates] = true
|
|
@@ -117,9 +116,6 @@ class Morpheus::Cli::InvoicesCommand
|
|
|
117
116
|
options[:tags][k] << (v || '')
|
|
118
117
|
end
|
|
119
118
|
end
|
|
120
|
-
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
|
121
|
-
options[:show_raw_data] = true
|
|
122
|
-
end
|
|
123
119
|
opts.on('--totals', "View total costs and prices for all the invoices found.") do |val|
|
|
124
120
|
params['includeTotals'] = true
|
|
125
121
|
options[:show_invoice_totals] = true
|
|
@@ -173,7 +169,6 @@ class Morpheus::Cli::InvoicesCommand
|
|
|
173
169
|
return 1, "projects not found for #{options[:projects]}" if project_ids.nil?
|
|
174
170
|
params['projectId'] = project_ids
|
|
175
171
|
end
|
|
176
|
-
params['rawData'] = true if options[:show_raw_data]
|
|
177
172
|
params['refId'] = ref_ids unless ref_ids.empty?
|
|
178
173
|
if options[:tags] && !options[:tags].empty?
|
|
179
174
|
options[:tags].each do |k,v|
|
|
@@ -271,8 +266,8 @@ class Morpheus::Cli::InvoicesCommand
|
|
|
271
266
|
columns += [
|
|
272
267
|
{"ESTIMATE" => lambda {|it| format_boolean(it['estimate']) } },
|
|
273
268
|
{"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
|
|
274
|
-
{"ITEMS" => lambda {|it| it['lineItems'].size rescue '' } },
|
|
275
|
-
{"TAGS" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' } },
|
|
269
|
+
{"ITEMS" => lambda {|it| (it['lineItemCount'] ? it['lineItemCount'] : it['lineItems'].size) rescue '' } },
|
|
270
|
+
{"TAGS" => lambda {|it| (it['metadata'] || it['tags']) ? (it['metadata'] || it['tags']).collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' } },
|
|
276
271
|
]
|
|
277
272
|
if show_projects
|
|
278
273
|
columns += [
|
|
@@ -291,9 +286,6 @@ class Morpheus::Cli::InvoicesCommand
|
|
|
291
286
|
{"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
|
|
292
287
|
{"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } },
|
|
293
288
|
]
|
|
294
|
-
if options[:show_raw_data]
|
|
295
|
-
columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
|
296
|
-
end
|
|
297
289
|
unless options[:totals_only]
|
|
298
290
|
print as_pretty_table(invoices, columns, options)
|
|
299
291
|
print_results_pagination(json_response, {:label => "invoice", :n_label => "invoices"})
|
|
@@ -361,7 +353,6 @@ class Morpheus::Cli::InvoicesCommand
|
|
|
361
353
|
options[:show_estimates] = true
|
|
362
354
|
# options[:show_costs] = true
|
|
363
355
|
options[:show_prices] = true
|
|
364
|
-
# options[:show_raw_data] = true
|
|
365
356
|
options[:max_line_items] = 10000
|
|
366
357
|
end
|
|
367
358
|
opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
|
|
@@ -370,13 +361,6 @@ class Morpheus::Cli::InvoicesCommand
|
|
|
370
361
|
opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Extra" ) do
|
|
371
362
|
options[:show_estimates] = true
|
|
372
363
|
end
|
|
373
|
-
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
|
374
|
-
options[:show_raw_data] = true
|
|
375
|
-
end
|
|
376
|
-
opts.on('--pretty-raw-data', '--raw-data', "Display Raw Data that is a bit more pretty") do |val|
|
|
377
|
-
options[:show_raw_data] = true
|
|
378
|
-
options[:pretty_json] = true
|
|
379
|
-
end
|
|
380
364
|
opts.on('--no-line-items', '--no-line-items', "Do not display line items.") do |val|
|
|
381
365
|
options[:hide_line_items] = true
|
|
382
366
|
end
|
|
@@ -401,9 +385,6 @@ EOT
|
|
|
401
385
|
|
|
402
386
|
def _get(id, options)
|
|
403
387
|
params = {}
|
|
404
|
-
if options[:show_raw_data]
|
|
405
|
-
params['rawData'] = true
|
|
406
|
-
end
|
|
407
388
|
begin
|
|
408
389
|
@invoices_interface.setopts(options)
|
|
409
390
|
if options[:dry_run]
|
|
@@ -412,6 +393,9 @@ EOT
|
|
|
412
393
|
end
|
|
413
394
|
json_response = @invoices_interface.get(id, params)
|
|
414
395
|
invoice = json_response['invoice']
|
|
396
|
+
if options[:hide_line_items]
|
|
397
|
+
json_response['invoice'].delete('lineItems') rescue nil
|
|
398
|
+
end
|
|
415
399
|
render_result = render_with_format(json_response, options, 'invoice')
|
|
416
400
|
return 0 if render_result
|
|
417
401
|
|
|
@@ -437,8 +421,8 @@ EOT
|
|
|
437
421
|
"End" => lambda {|it| format_date(it['endDate']) },
|
|
438
422
|
"Ref Start" => lambda {|it| format_dt(it['refStart']) },
|
|
439
423
|
"Ref End" => lambda {|it| format_dt(it['refEnd']) },
|
|
440
|
-
"Items" => lambda {|it| it['lineItems'].size rescue '' },
|
|
441
|
-
"Tags" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
|
424
|
+
"Items" => lambda {|it| (it['lineItemCount'] ? it['lineItemCount'] : it['lineItems'].size) rescue '' },
|
|
425
|
+
"Tags" => lambda {|it| (it['metadata'] || it['tags']) ? (it['metadata'] || it['tags']).collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
|
442
426
|
"Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
|
|
443
427
|
"Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
|
|
444
428
|
"Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
|
|
@@ -454,7 +438,8 @@ EOT
|
|
|
454
438
|
description_cols.delete("Project Name")
|
|
455
439
|
description_cols.delete("Project Tags")
|
|
456
440
|
end
|
|
457
|
-
|
|
441
|
+
tags = (invoice['metadata'] || invoice['tags'])
|
|
442
|
+
if tags.nil? || tags.empty?
|
|
458
443
|
description_cols.delete("Tags")
|
|
459
444
|
end
|
|
460
445
|
if !['ComputeServer','Instance','Container'].include?(invoice['refType'])
|
|
@@ -522,9 +507,6 @@ EOT
|
|
|
522
507
|
{"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
|
|
523
508
|
{"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } }
|
|
524
509
|
]
|
|
525
|
-
if options[:show_raw_data]
|
|
526
|
-
line_items_columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
|
527
|
-
end
|
|
528
510
|
print_h2 "Line Items"
|
|
529
511
|
#max_line_items = options[:max_line_items] ? options[:max_line_items].to_i : 5
|
|
530
512
|
paged_line_items = line_items #.first(max_line_items)
|
|
@@ -577,10 +559,7 @@ EOT
|
|
|
577
559
|
end
|
|
578
560
|
print as_pretty_table(cost_rows, cost_columns, options)
|
|
579
561
|
|
|
580
|
-
|
|
581
|
-
print_h2 "Raw Data"
|
|
582
|
-
puts as_json(invoice['rawData'], {pretty_json:false}.merge(options))
|
|
583
|
-
end
|
|
562
|
+
|
|
584
563
|
|
|
585
564
|
print reset,"\n"
|
|
586
565
|
return 0
|
|
@@ -590,6 +569,68 @@ EOT
|
|
|
590
569
|
end
|
|
591
570
|
end
|
|
592
571
|
|
|
572
|
+
def update(args)
|
|
573
|
+
options = {}
|
|
574
|
+
params = {}
|
|
575
|
+
payload = {}
|
|
576
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
577
|
+
opts.banner = subcommand_usage("[invoice] [options]")
|
|
578
|
+
opts.on('--tags LIST', String, "Tags in the format 'name:value, name:value'. This will add and remove tags.") do |val|
|
|
579
|
+
options[:tags] = val
|
|
580
|
+
end
|
|
581
|
+
opts.on('--add-tags TAGS', String, "Add Tags in the format 'name:value, name:value'. This will only add/update tags.") do |val|
|
|
582
|
+
options[:add_tags] = val
|
|
583
|
+
end
|
|
584
|
+
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|
|
|
585
|
+
options[:remove_tags] = val
|
|
586
|
+
end
|
|
587
|
+
build_standard_update_options(opts, options)
|
|
588
|
+
opts.footer = <<-EOT
|
|
589
|
+
Update an invoice.
|
|
590
|
+
[invoice] is required. This is the id of an invoice.
|
|
591
|
+
EOT
|
|
592
|
+
end
|
|
593
|
+
optparse.parse!(args)
|
|
594
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
595
|
+
connect(options)
|
|
596
|
+
json_response = @invoices_interface.get(args[0])
|
|
597
|
+
invoice = json_response['invoice']
|
|
598
|
+
|
|
599
|
+
invoice_payload = parse_passed_options(options)
|
|
600
|
+
if options[:tags]
|
|
601
|
+
invoice_payload['tags'] = parse_metadata(options[:tags])
|
|
602
|
+
end
|
|
603
|
+
if options[:add_tags]
|
|
604
|
+
invoice_payload['addTags'] = parse_metadata(options[:add_tags])
|
|
605
|
+
end
|
|
606
|
+
if options[:remove_tags]
|
|
607
|
+
invoice_payload['removeTags'] = parse_metadata(options[:remove_tags])
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
payload = {}
|
|
611
|
+
if options[:payload]
|
|
612
|
+
payload = options[:payload]
|
|
613
|
+
payload.deep_merge!({'invoice' => invoice_payload})
|
|
614
|
+
else
|
|
615
|
+
payload.deep_merge!({'invoice' => invoice_payload})
|
|
616
|
+
if invoice_payload.empty?
|
|
617
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
@invoices_interface.setopts(options)
|
|
621
|
+
if options[:dry_run]
|
|
622
|
+
print_dry_run @invoices_interface.dry.update(invoice['id'], payload)
|
|
623
|
+
return
|
|
624
|
+
end
|
|
625
|
+
json_response = @invoices_interface.update(invoice['id'], payload)
|
|
626
|
+
invoice = json_response['invoice']
|
|
627
|
+
render_response(json_response, options, 'invoice') do
|
|
628
|
+
print_green_success "Updated invoice #{invoice['id']}"
|
|
629
|
+
return _get(invoice["id"], options)
|
|
630
|
+
end
|
|
631
|
+
return 0, nil
|
|
632
|
+
end
|
|
633
|
+
|
|
593
634
|
def refresh(args)
|
|
594
635
|
options = {}
|
|
595
636
|
params = {}
|
|
@@ -677,7 +718,6 @@ EOT
|
|
|
677
718
|
options[:show_actual_costs] = true
|
|
678
719
|
options[:show_costs] = true
|
|
679
720
|
options[:show_prices] = true
|
|
680
|
-
# options[:show_raw_data] = true
|
|
681
721
|
end
|
|
682
722
|
# opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Storage, Network, Extra" ) do
|
|
683
723
|
# options[:show_actual_costs] = true
|
|
@@ -766,9 +806,6 @@ EOT
|
|
|
766
806
|
options[:tags][k] << (v || '')
|
|
767
807
|
end
|
|
768
808
|
end
|
|
769
|
-
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
|
770
|
-
options[:show_raw_data] = true
|
|
771
|
-
end
|
|
772
809
|
opts.on('--totals', "View total costs and prices for all the invoices found.") do |val|
|
|
773
810
|
params['includeTotals'] = true
|
|
774
811
|
options[:show_invoice_totals] = true
|
|
@@ -823,7 +860,6 @@ EOT
|
|
|
823
860
|
return 1, "projects not found for #{options[:projects]}" if project_ids.nil?
|
|
824
861
|
params['projectId'] = project_ids
|
|
825
862
|
end
|
|
826
|
-
params['rawData'] = true if options[:show_raw_data]
|
|
827
863
|
params['refId'] = ref_ids unless ref_ids.empty?
|
|
828
864
|
if options[:tags] && !options[:tags].empty?
|
|
829
865
|
options[:tags].each do |k,v|
|
|
@@ -881,9 +917,6 @@ EOT
|
|
|
881
917
|
"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
|
882
918
|
]
|
|
883
919
|
|
|
884
|
-
if options[:show_raw_data]
|
|
885
|
-
columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
|
|
886
|
-
end
|
|
887
920
|
# if options[:show_invoice_totals]
|
|
888
921
|
# line_item_totals = json_response['lineItemTotals']
|
|
889
922
|
# if line_item_totals
|
|
@@ -932,13 +965,6 @@ EOT
|
|
|
932
965
|
options = {}
|
|
933
966
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
934
967
|
opts.banner = subcommand_usage("[id]")
|
|
935
|
-
opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
|
|
936
|
-
options[:show_raw_data] = true
|
|
937
|
-
end
|
|
938
|
-
opts.on('--pretty-raw-data', '--raw-data', "Display Raw Data that is a bit more pretty") do |val|
|
|
939
|
-
options[:show_raw_data] = true
|
|
940
|
-
options[:pretty_json] = true
|
|
941
|
-
end
|
|
942
968
|
opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
|
|
943
969
|
options[:sigdig] = val.to_i
|
|
944
970
|
end
|
|
@@ -960,9 +986,6 @@ EOT
|
|
|
960
986
|
|
|
961
987
|
def _get_line_item(id, options)
|
|
962
988
|
params = {}
|
|
963
|
-
if options[:show_raw_data]
|
|
964
|
-
params['rawData'] = true
|
|
965
|
-
end
|
|
966
989
|
@invoice_line_items_interface.setopts(options)
|
|
967
990
|
if options[:dry_run]
|
|
968
991
|
print_dry_run @invoice_line_items_interface.dry.get(id, params)
|
|
@@ -1000,11 +1023,6 @@ EOT
|
|
|
1000
1023
|
}
|
|
1001
1024
|
print_description_list(description_cols, line_item, options)
|
|
1002
1025
|
|
|
1003
|
-
if options[:show_raw_data]
|
|
1004
|
-
print_h2 "Raw Data"
|
|
1005
|
-
puts as_json(line_item['rawData'], {pretty_json:false}.merge(options))
|
|
1006
|
-
end
|
|
1007
|
-
|
|
1008
1026
|
print reset,"\n"
|
|
1009
1027
|
end
|
|
1010
1028
|
return 0, nil
|