morpheus-cli 5.2.1 → 5.3.0
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/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/activity_command.rb +7 -4
- 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/execution_request_command.rb +15 -5
- data/lib/morpheus/cli/hosts.rb +12 -0
- data/lib/morpheus/cli/instances.rb +116 -2
- data/lib/morpheus/cli/invoices_command.rb +89 -95
- 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 +16 -6
- data/lib/morpheus/cli/projects_command.rb +1 -1
- data/lib/morpheus/cli/remote.rb +2 -1
- data/lib/morpheus/cli/user_sources_command.rb +118 -134
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/formatters.rb +21 -11
- 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: 4c04cf89b798df6d8dba342efe64eec23b5f97bbeef368072d56829c8b517707
|
4
|
+
data.tar.gz: 50a9448b2a167439efbd3f3bcd2f0ea91870681986108d975b058a40b5e14d69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58613849cca0d5994cc866d6de770f0657b65ccbd3961e352ac934f21600f0888be755c70f54cc964a317fa12201a78800eb59447a664ec28df7eb00799cdc1f
|
7
|
+
data.tar.gz: 01307e6f8b6c91813a948d79b5dedc0914d5731b026d6152ddf1ea59d8ab9a830bca5305a28087623d088787464dc76ee3667673759467a2910723b06645a3b7
|
data/Dockerfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
+
<img src="https://morpheusdata.com/wp-content/uploads/2020/04/morpheus-logo-v2.svg" width="200px">
|
2
|
+
|
1
3
|
# Morpheus CLI
|
2
4
|
|
3
5
|
- Website: https://www.morpheusdata.com/
|
4
6
|
- Guide: [Morpheus CLI Wiki](https://github.com/gomorpheus/morpheus-cli/wiki)
|
5
|
-
- Docs: [Morpheus Documentation](https://
|
7
|
+
- Docs: [Morpheus CLI Documentation](https://clidocs.morpheusdata.com)
|
6
8
|
- Support: [Morpheus Support](https://support.morpheusdata.com)
|
7
9
|
|
8
|
-
<img src="https://www.morpheusdata.com/wp-content/uploads/2018/06/cropped-morpheus_highres.png" width="600px">
|
9
|
-
|
10
10
|
This library is a Ruby gem that provides a command line interface for interacting with the Morpheus Data appliance. The features provided include provisioning clusters, hosts, and containers, deploying and monitoring applications, automating tasks, and much more.
|
11
11
|
|
12
12
|
## Installation
|
@@ -190,6 +190,27 @@ class Morpheus::InstancesInterface < Morpheus::APIClient
|
|
190
190
|
execute(opts)
|
191
191
|
end
|
192
192
|
|
193
|
+
def clone_image(id, payload)
|
194
|
+
url = "#{@base_url}/api/instances/#{id}/clone-image"
|
195
|
+
headers = {:authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
196
|
+
opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
|
197
|
+
execute(opts)
|
198
|
+
end
|
199
|
+
|
200
|
+
def lock(id, payload)
|
201
|
+
url = "#{@base_url}/api/instances/#{id}/lock"
|
202
|
+
headers = {:authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
203
|
+
opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
|
204
|
+
execute(opts)
|
205
|
+
end
|
206
|
+
|
207
|
+
def unlock(id, payload)
|
208
|
+
url = "#{@base_url}/api/instances/#{id}/unlock"
|
209
|
+
headers = {:authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
210
|
+
opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
|
211
|
+
execute(opts)
|
212
|
+
end
|
213
|
+
|
193
214
|
def firewall_disable(id)
|
194
215
|
url = "#{@base_url}/api/instances/#{id}/security-groups/disable"
|
195
216
|
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
@@ -2,18 +2,27 @@ require 'morpheus/api/api_client'
|
|
2
2
|
|
3
3
|
class Morpheus::InvoicesInterface < Morpheus::APIClient
|
4
4
|
|
5
|
+
def base_path
|
6
|
+
"/api/invoices"
|
7
|
+
end
|
8
|
+
|
5
9
|
def list(params={})
|
6
|
-
execute(method: :get, url: "
|
10
|
+
execute(method: :get, url: "#{base_path}", headers: {params: params})
|
7
11
|
end
|
8
12
|
|
9
13
|
def get(id, params={})
|
10
14
|
raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
|
11
|
-
execute(method: :get, url: "
|
15
|
+
execute(method: :get, url: "#{base_path}/#{id}", headers: {params: params})
|
16
|
+
end
|
17
|
+
|
18
|
+
def update(id, payload)
|
19
|
+
validate_id!(id)
|
20
|
+
execute(url: "#{base_path}/#{id}", payload: payload.to_json, method: :put)
|
12
21
|
end
|
13
22
|
|
14
23
|
def refresh(params={}, payload={})
|
15
24
|
headers = {:params => params, 'Content-Type' => 'application/json'}
|
16
|
-
execute(method: :post, url: "/
|
25
|
+
execute(method: :post, url: "#{base_path}/refresh", headers: headers, payload: payload.to_json)
|
17
26
|
end
|
18
27
|
|
19
28
|
end
|
@@ -22,6 +22,9 @@ class Morpheus::Cli::ActivityCommand
|
|
22
22
|
params, options = {}, {}
|
23
23
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
24
24
|
opts.banner = subcommand_usage()
|
25
|
+
opts.on('-a', '--details', "Display more details object id, full date and time, etc." ) do
|
26
|
+
options[:details] = true
|
27
|
+
end
|
25
28
|
opts.on('-t','--type TYPE', "Activity Type eg. Provisioning, Admin") do |val|
|
26
29
|
options[:type] ||= []
|
27
30
|
options[:type] << val
|
@@ -115,20 +118,20 @@ EOT
|
|
115
118
|
# {"SEVERITY" => lambda {|record| format_activity_severity(record['severity']) } },
|
116
119
|
{"TYPE" => lambda {|record| record['activityType'] } },
|
117
120
|
{"NAME" => lambda {|record| record['name'] } },
|
118
|
-
{"RESOURCE" => lambda {|record| "#{record['objectType']} #{record['objectId']}" } },
|
121
|
+
options[:details] ? {"RESOURCE" => lambda {|record| "#{record['objectType']} #{record['objectId']}" } } : nil,
|
119
122
|
{"MESSAGE" => lambda {|record| record['message'] || '' } },
|
120
123
|
{"USER" => lambda {|record| record['user'] ? record['user']['username'] : record['userName'] } },
|
121
124
|
#{"DATE" => lambda {|record| "#{format_duration_ago(record['ts'] || record['timestamp'])}" } },
|
122
125
|
{"DATE" => lambda {|record|
|
123
|
-
# show full time if searching for custom timerange, otherwise the default is to show relative time
|
124
|
-
if params['start'] || params['end'] || params['timeframe']
|
126
|
+
# show full time if searching for custom timerange or --details, otherwise the default is to show relative time
|
127
|
+
if params['start'] || params['end'] || params['timeframe'] || options[:details]
|
125
128
|
"#{format_local_dt(record['ts'] || record['timestamp'])}"
|
126
129
|
else
|
127
130
|
"#{format_duration_ago(record['ts'] || record['timestamp'])}"
|
128
131
|
end
|
129
132
|
|
130
133
|
} },
|
131
|
-
]
|
134
|
+
].compact
|
132
135
|
print as_pretty_table(activity, columns, options)
|
133
136
|
print_results_pagination(json_response)
|
134
137
|
end
|
@@ -10,7 +10,7 @@ class Morpheus::Cli::BackupJobsCommand
|
|
10
10
|
|
11
11
|
set_command_name :'backup-jobs'
|
12
12
|
|
13
|
-
register_subcommands :list, :get
|
13
|
+
register_subcommands :list, :get #, :add, :update, :remove, :run
|
14
14
|
|
15
15
|
def connect(opts)
|
16
16
|
@api_client = establish_remote_appliance_connection(opts)
|
@@ -10,7 +10,7 @@ class Morpheus::Cli::BackupsCommand
|
|
10
10
|
|
11
11
|
set_command_name :'backups'
|
12
12
|
|
13
|
-
register_subcommands :list, :get
|
13
|
+
register_subcommands :list, :get #, :add, :update, :remove, :run, :restore
|
14
14
|
|
15
15
|
def connect(opts)
|
16
16
|
@api_client = establish_remote_appliance_connection(opts)
|
@@ -82,8 +82,7 @@ EOT
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
def _get(id, options)
|
86
|
-
params = {}
|
85
|
+
def _get(id, params, options)
|
87
86
|
@backups_interface.setopts(options)
|
88
87
|
if options[:dry_run]
|
89
88
|
print_dry_run @backups_interface.dry.get(id, params)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'morpheus/cli/cli_command'
|
2
2
|
require 'money' # ew, let's write our own
|
3
|
+
require 'time'
|
3
4
|
|
4
5
|
class Morpheus::Cli::BudgetsCommand
|
5
6
|
include Morpheus::Cli::CliCommand
|
@@ -21,76 +22,72 @@ class Morpheus::Cli::BudgetsCommand
|
|
21
22
|
params = {}
|
22
23
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
23
24
|
opts.banner = subcommand_usage()
|
24
|
-
|
25
|
+
build_standard_list_options(opts, options)
|
25
26
|
opts.footer = "List budgets."
|
26
27
|
end
|
27
28
|
optparse.parse!(args)
|
28
|
-
|
29
|
-
|
29
|
+
# verify_args!(args:args, optparse:optparse, count:0)
|
30
|
+
if args.count > 0
|
31
|
+
options[:phrase] = args.join(" ")
|
30
32
|
end
|
33
|
+
params.merge!(parse_list_options(options))
|
31
34
|
connect(options)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
]
|
81
|
-
if options[:include_fields]
|
82
|
-
columns = options[:include_fields]
|
83
|
-
end
|
84
|
-
print as_pretty_table(budgets, columns, options)
|
85
|
-
print_results_pagination(json_response)
|
35
|
+
|
36
|
+
params.merge!(parse_list_options(options))
|
37
|
+
@budgets_interface.setopts(options)
|
38
|
+
if options[:dry_run]
|
39
|
+
print_dry_run @budgets_interface.dry.list(params)
|
40
|
+
return 0
|
41
|
+
end
|
42
|
+
json_response = @budgets_interface.list(params)
|
43
|
+
budgets = json_response['budgets']
|
44
|
+
render_response(json_response, options, 'budgets') do
|
45
|
+
title = "Morpheus Budgets"
|
46
|
+
subtitles = []
|
47
|
+
subtitles += parse_list_subtitles(options)
|
48
|
+
print_h1 title, subtitles
|
49
|
+
if budgets.empty?
|
50
|
+
print cyan,"No budgets found.",reset,"\n"
|
51
|
+
else
|
52
|
+
columns = [
|
53
|
+
{"ID" => lambda {|budget| budget['id'] } },
|
54
|
+
{"NAME" => lambda {|budget| budget['name'] } },
|
55
|
+
{"DESCRIPTION" => lambda {|budget| truncate_string(budget['description'], 30) } },
|
56
|
+
# {"ENABLED" => lambda {|budget| format_boolean(budget['enabled']) } },
|
57
|
+
# {"SCOPE" => lambda {|it| format_budget_scope(it) } },
|
58
|
+
{"SCOPE" => lambda {|it| it['refName'] } },
|
59
|
+
{"PERIOD" => lambda {|it| it['year'] } },
|
60
|
+
{"INTERVAL" => lambda {|it| it['interval'].to_s.capitalize } },
|
61
|
+
# the UI doesn't consider timezone, so uhh do it this hacky way for now.
|
62
|
+
{"START DATE" => lambda {|it|
|
63
|
+
if it['timezone'] == 'UTC'
|
64
|
+
((parse_time(it['startDate'], "%Y-%m-%d").strftime("%x")) rescue it['startDate']) # + ' UTC'
|
65
|
+
else
|
66
|
+
format_local_date(it['startDate'])
|
67
|
+
end
|
68
|
+
} },
|
69
|
+
{"END DATE" => lambda {|it|
|
70
|
+
if it['timezone'] == 'UTC'
|
71
|
+
((parse_time(it['endDate'], "%Y-%m-%d").strftime("%x")) rescue it['endDate']) # + ' UTC'
|
72
|
+
else
|
73
|
+
format_local_date(it['endDate'])
|
74
|
+
end
|
75
|
+
} },
|
76
|
+
{"TOTAL" => lambda {|it| format_money(it['totalCost'], it['currency']) } },
|
77
|
+
{"AVERAGE" => lambda {|it| format_money(it['averageCost'], it['currency']) } },
|
78
|
+
# {"CREATED BY" => lambda {|budget| budget['createdByName'] ? budget['createdByName'] : budget['createdById'] } },
|
79
|
+
# {"CREATED" => lambda {|budget| format_local_dt(budget['dateCreated']) } },
|
80
|
+
# {"UPDATED" => lambda {|budget| format_local_dt(budget['lastUpdated']) } },
|
81
|
+
]
|
82
|
+
if options[:include_fields]
|
83
|
+
columns = options[:include_fields]
|
86
84
|
end
|
87
|
-
print
|
85
|
+
print as_pretty_table(budgets, columns, options)
|
86
|
+
print_results_pagination(json_response)
|
88
87
|
end
|
89
|
-
|
90
|
-
rescue RestClient::Exception => e
|
91
|
-
print_rest_exception(e, options)
|
92
|
-
exit 1
|
88
|
+
print reset,"\n"
|
93
89
|
end
|
90
|
+
return budgets.empty? ? [3, "no budgets found"] : [0, nil]
|
94
91
|
end
|
95
92
|
|
96
93
|
def get(args)
|
@@ -98,37 +95,32 @@ class Morpheus::Cli::BudgetsCommand
|
|
98
95
|
params = {}
|
99
96
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
100
97
|
opts.banner = subcommand_usage("[budget]")
|
101
|
-
|
98
|
+
build_standard_get_options(opts, options)
|
102
99
|
opts.footer = "Get details about a budget.\n[budget] is required. Budget ID or name"
|
103
100
|
end
|
104
101
|
optparse.parse!(args)
|
105
|
-
|
106
|
-
if args.count != 1
|
107
|
-
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
|
108
|
-
end
|
109
|
-
|
102
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
110
103
|
connect(options)
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end
|
119
|
-
return 0
|
120
|
-
end
|
121
|
-
budget = find_budget_by_name_or_id(args[0])
|
122
|
-
return 1 if budget.nil?
|
123
|
-
# skip reload if already fetched via get(id)
|
124
|
-
json_response = {'budget' => budget}
|
125
|
-
if args[0].to_s != budget['id'].to_s
|
126
|
-
json_response = @budgets_interface.get(budget['id'], params)
|
127
|
-
budget = json_response['budget']
|
104
|
+
params.merge!(parse_query_options(options))
|
105
|
+
@budgets_interface.setopts(options)
|
106
|
+
if options[:dry_run]
|
107
|
+
if args[0].to_s =~ /\A\d{1,}\Z/
|
108
|
+
print_dry_run @budgets_interface.dry.get(args[0], params)
|
109
|
+
else
|
110
|
+
print_dry_run @budgets_interface.dry.list({name: args[0].to_s})
|
128
111
|
end
|
129
|
-
|
130
|
-
|
131
|
-
|
112
|
+
return 0
|
113
|
+
end
|
114
|
+
budget = find_budget_by_name_or_id(args[0])
|
115
|
+
return 1 if budget.nil?
|
116
|
+
# skip reload if already fetched via get(id)
|
117
|
+
json_response = {'budget' => budget}
|
118
|
+
if args[0].to_s != budget['id'].to_s
|
119
|
+
json_response = @budgets_interface.get(budget['id'], params)
|
120
|
+
budget = json_response['budget']
|
121
|
+
end
|
122
|
+
|
123
|
+
render_response(json_response, options, 'budget') do
|
132
124
|
|
133
125
|
print_h1 "Budget Details"
|
134
126
|
print cyan
|
@@ -140,52 +132,6 @@ class Morpheus::Cli::BudgetsCommand
|
|
140
132
|
"Scope" => lambda {|it| format_budget_scope(it) },
|
141
133
|
"Period" => lambda {|it| it['year'] },
|
142
134
|
"Interval" => lambda {|it| it['interval'].to_s.capitalize },
|
143
|
-
# "Costs" => lambda {|it| it['costs'].inspect },
|
144
|
-
}
|
145
|
-
if budget['interval'] == 'year'
|
146
|
-
budget_columns.merge!({
|
147
|
-
"Annual" => lambda {|it|
|
148
|
-
(it['costs'] && it['costs']['year']) ? format_money(it['costs']['year'], it['currency']) : ''
|
149
|
-
},
|
150
|
-
})
|
151
|
-
elsif budget['interval'] == 'quarter'
|
152
|
-
budget_columns.merge!({
|
153
|
-
"Q1" => lambda {|it| (it['costs'] && it['costs']['q1']) ? format_money(it['costs']['q1'], it['currency']) : '' },
|
154
|
-
"Q2" => lambda {|it| (it['costs'] && it['costs']['q2']) ? format_money(it['costs']['q2'], it['currency']) : '' },
|
155
|
-
"Q3" => lambda {|it| (it['costs'] && it['costs']['q3']) ? format_money(it['costs']['q3'], it['currency']) : '' },
|
156
|
-
"Q4" => lambda {|it| (it['costs'] && it['costs']['q4']) ? format_money(it['costs']['q4'], it['currency']) : '' },
|
157
|
-
})
|
158
|
-
elsif budget['interval'] == 'month'
|
159
|
-
budget_columns.merge!({
|
160
|
-
"January" => lambda {|it| (it['costs'] && it['costs']['january']) ? format_money(it['costs']['january'], it['currency']) : '' },
|
161
|
-
"February" => lambda {|it| (it['costs'] && it['costs']['february']) ? format_money(it['costs']['february'], it['currency']) : '' },
|
162
|
-
"March" => lambda {|it| (it['costs'] && it['costs']['march']) ? format_money(it['costs']['march'], it['currency']) : '' },
|
163
|
-
"April" => lambda {|it| (it['costs'] && it['costs']['april']) ? format_money(it['costs']['april'], it['currency']) : '' },
|
164
|
-
"May" => lambda {|it| (it['costs'] && it['costs']['may']) ? format_money(it['costs']['may'], it['currency']) : '' },
|
165
|
-
"June" => lambda {|it| (it['costs'] && it['costs']['june']) ? format_money(it['costs']['june'], it['currency']) : '' },
|
166
|
-
"July" => lambda {|it| (it['costs'] && it['costs']['july']) ? format_money(it['costs']['july'], it['currency']) : '' },
|
167
|
-
"August" => lambda {|it| (it['costs'] && it['costs']['august']) ? format_money(it['costs']['august'], it['currency']) : '' },
|
168
|
-
"September" => lambda {|it| (it['costs'] && it['costs']['september']) ? format_money(it['costs']['september'], it['currency']) : '' },
|
169
|
-
"October" => lambda {|it| (it['costs'] && it['costs']['october']) ? format_money(it['costs']['october'], it['currency']) : '' },
|
170
|
-
"November" => lambda {|it| (it['costs'] && it['costs']['november']) ? format_money(it['costs']['nov'], it['currency']) : '' },
|
171
|
-
"December" => lambda {|it| (it['costs'] && it['costs']['december']) ? format_money(it['costs']['december'], it['currency']) : '' }
|
172
|
-
})
|
173
|
-
else
|
174
|
-
budget_columns.merge!({
|
175
|
-
"Costs" => lambda {|it|
|
176
|
-
if it['costs'].is_a?(Array)
|
177
|
-
it['costs'] ? it['costs'].join(', ') : ''
|
178
|
-
elsif it['costs'].is_a?(Hash)
|
179
|
-
it['costs'].to_s
|
180
|
-
else
|
181
|
-
it['costs'].to_s
|
182
|
-
end
|
183
|
-
},
|
184
|
-
})
|
185
|
-
end
|
186
|
-
budget_columns.merge!({
|
187
|
-
"Total" => lambda {|it| format_money(it['totalCost'], it['currency']) },
|
188
|
-
"Average" => lambda {|it| format_money(it['averageCost'], it['currency']) },
|
189
135
|
# the UI doesn't consider timezone, so uhh do it this hacky way for now.
|
190
136
|
"Start Date" => lambda {|it|
|
191
137
|
if it['timezone'] == 'UTC'
|
@@ -201,13 +147,19 @@ class Morpheus::Cli::BudgetsCommand
|
|
201
147
|
format_local_date(it['endDate'])
|
202
148
|
end
|
203
149
|
},
|
204
|
-
"
|
205
|
-
|
206
|
-
|
150
|
+
# "Costs" => lambda {|it|
|
151
|
+
# if it['costs'].is_a?(Array)
|
152
|
+
# it['costs'] ? it['costs'].join(', ') : ''
|
153
|
+
# elsif it['costs'].is_a?(Hash)
|
154
|
+
# it['costs'].to_s
|
155
|
+
# else
|
156
|
+
# it['costs'].to_s
|
157
|
+
# end
|
158
|
+
# },
|
207
159
|
"Created By" => lambda {|it| it['createdByName'] ? it['createdByName'] : it['createdById'] },
|
208
160
|
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
209
161
|
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
|
210
|
-
}
|
162
|
+
}
|
211
163
|
print_description_list(budget_columns, budget, options)
|
212
164
|
# print reset,"\n"
|
213
165
|
|
@@ -221,33 +173,17 @@ class Morpheus::Cli::BudgetsCommand
|
|
221
173
|
}
|
222
174
|
budget_row = {label:"Budget"}
|
223
175
|
actual_row = {label:"Actual"}
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
# budget_summary_columns[interval_key] = lambda {|it|
|
230
|
-
# display_val = format_money(it[interval_key], budget['stats']['currency'])
|
231
|
-
# over_budget = actual_row[interval_key] && (actual_row[interval_key] > (budget_row[interval_key] || 0))
|
232
|
-
# if over_budget
|
233
|
-
# "#{red}#{display_val}#{cyan}"
|
234
|
-
# else
|
235
|
-
# "#{cyan}#{display_val}#{cyan}"
|
236
|
-
# end
|
237
|
-
# }
|
238
|
-
# budget_row[interval_key] = stat_interval["budget"].to_f
|
239
|
-
# actual_row[interval_key] = stat_interval["cost"].to_f
|
240
|
-
# end
|
241
|
-
budget['stats']['intervals'].each do |stat_interval|
|
176
|
+
multi_year = false
|
177
|
+
if budget['startDate'] && budget['endDate'] && parse_time(budget['startDate']).year != parse_time(budget['endDate']).year
|
178
|
+
multi_year = true
|
179
|
+
end
|
180
|
+
budget['stats']['intervals'].each do |it|
|
242
181
|
currency = budget['currency'] || budget['stats']['currency']
|
243
|
-
interval_key = (
|
244
|
-
|
245
|
-
interval_key = "Year #{budget['year']}"
|
246
|
-
end
|
247
|
-
# add simple column definition, just use the key
|
182
|
+
interval_key = format_budget_interval_label(budget, it).to_s.upcase
|
183
|
+
# interval_date = parse_time(it["startDate"]) rescue nil
|
248
184
|
budget_summary_columns[interval_key] = interval_key
|
249
|
-
budget_cost =
|
250
|
-
actual_cost =
|
185
|
+
budget_cost = it["budget"].to_f
|
186
|
+
actual_cost = it["cost"].to_f
|
251
187
|
over_budget = actual_cost > 0 && actual_cost > budget_cost
|
252
188
|
if over_budget
|
253
189
|
budget_row[interval_key] = "#{cyan}#{format_money(budget_cost, currency)}#{cyan}"
|
@@ -262,44 +198,65 @@ class Morpheus::Cli::BudgetsCommand
|
|
262
198
|
print reset,"\n"
|
263
199
|
rescue => ex
|
264
200
|
print red,"Failed to render budget summary.",reset,"\n"
|
201
|
+
raise ex
|
265
202
|
end
|
266
203
|
else
|
267
204
|
print cyan,"No budget stat data found.",reset,"\n"
|
268
205
|
end
|
269
|
-
return 0
|
270
|
-
rescue RestClient::Exception => e
|
271
|
-
print_rest_exception(e, options)
|
272
|
-
exit 1
|
273
206
|
end
|
207
|
+
return 0, nil
|
274
208
|
end
|
275
209
|
|
276
210
|
def add(args)
|
277
211
|
options = {}
|
278
212
|
params = {}
|
279
|
-
costs =
|
213
|
+
costs = []
|
280
214
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
281
215
|
opts.banner = subcommand_usage("[name] [options]")
|
282
216
|
build_option_type_options(opts, options, add_budget_option_types)
|
283
|
-
opts.on('--cost [amount]', String, "Budget cost amount, for use with default year interval.") do |val|
|
284
|
-
|
217
|
+
# opts.on('--cost [amount]', String, "Budget cost amount, for use with default year interval.") do |val|
|
218
|
+
# costs['year'] = (val.nil? || val.empty?) ? 0 : val.to_f
|
219
|
+
# end
|
220
|
+
opts.on('--costs LIST', String, "Budget cost amounts, one for each interval in the budget. eg \"350\" for one year, \"25,25,25,100\" for quarters, and \"10,10,10,10,10,10,10,10,10,10,10,50\" for each month") do |val|
|
221
|
+
val = val.to_s.gsub('[', '').gsub(']', '')
|
222
|
+
costs = val.to_s.split(',').collect {|it| parse_cost_amount(it) }
|
223
|
+
end
|
224
|
+
(1..12).each.with_index do |cost_index, i|
|
225
|
+
opts.on("--cost#{cost_index} VALUE", String, "Cost #{cost_index.to_s.capitalize} amount") do |val|
|
226
|
+
#params["cost#{cost_index.to_s}"] = parse_cost_amount(val)
|
227
|
+
costs[i] = parse_cost_amount(val)
|
228
|
+
end
|
229
|
+
opts.add_hidden_option("--cost#{cost_index}")
|
285
230
|
end
|
286
|
-
[:q1,:q2,:q3,:q4,
|
287
|
-
|
288
|
-
|
289
|
-
costs[quarter.to_s] = parse_cost_amount(val)
|
231
|
+
[:q1,:q2,:q3,:q4,].each.with_index do |quarter, i|
|
232
|
+
opts.on("--#{quarter.to_s} VALUE", String, "#{quarter.to_s.capitalize} cost amount, use with quarter interval.") do |val|
|
233
|
+
costs[i] = parse_cost_amount(val)
|
290
234
|
end
|
235
|
+
opts.add_hidden_option("--#{quarter.to_s}")
|
291
236
|
end
|
292
|
-
[:january,:february,:march,:april,:may,:june,:july,:august,:september,:october,:november,:december
|
293
|
-
|
294
|
-
|
295
|
-
costs[month.to_s] = parse_cost_amount(val)
|
237
|
+
[:january,:february,:march,:april,:may,:june,:july,:august,:september,:october,:november,:december].each_with_index do |month, i|
|
238
|
+
opts.on("--#{month.to_s} VALUE", String, "#{month.to_s.capitalize} cost amount, use with month interval.") do |val|
|
239
|
+
costs[i] = parse_cost_amount(val)
|
296
240
|
end
|
241
|
+
opts.add_hidden_option("--#{month.to_s}")
|
297
242
|
end
|
298
243
|
opts.on('--enabled [on|off]', String, "Can be used to disable a policy") do |val|
|
299
244
|
params['enabled'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s.empty?
|
300
245
|
end
|
301
|
-
|
302
|
-
opts.footer =
|
246
|
+
build_standard_add_options(opts, options)
|
247
|
+
opts.footer = <<-EOT
|
248
|
+
Create a budget.
|
249
|
+
The default period is the current year, eg. "#{Time.now.year}"
|
250
|
+
and the default interval is "year".
|
251
|
+
Costs can be passed as an array of values, one for each interval. eg. --costs "[999]"
|
252
|
+
|
253
|
+
Examples:
|
254
|
+
budgets add example-budget --interval "year" --costs "[2500]"
|
255
|
+
budgets add example-qtr-budget --interval "quarter" --costs "[500,500,500,1000]"
|
256
|
+
budgets add example-monthly-budget --interval "month" --costs "[400,100,100,100,100,100,100,100,100,100,400,800]"
|
257
|
+
budgets add example-future-budget --year "2022" --interval "year" --costs "[5000]"
|
258
|
+
budgets add example-custom-budget --year "custom" --interval "year" --start "2021-01-01" --end "2023-12-31" --costs "[2500,5000,10000]"
|
259
|
+
EOT
|
303
260
|
end
|
304
261
|
optparse.parse!(args)
|
305
262
|
if args.count > 1
|
@@ -322,21 +279,37 @@ class Morpheus::Cli::BudgetsCommand
|
|
322
279
|
}
|
323
280
|
}
|
324
281
|
# allow arbitrary -O options
|
325
|
-
passed_options.delete('costs')
|
282
|
+
#passed_options.delete('costs')
|
326
283
|
passed_options.delete('tenant')
|
327
284
|
passed_options.delete('group')
|
328
285
|
passed_options.delete('cloud')
|
329
286
|
passed_options.delete('user')
|
330
287
|
payload.deep_merge!({'budget' => passed_options}) unless passed_options.empty?
|
331
288
|
# prompt for options
|
332
|
-
if !costs.empty?
|
333
|
-
options[:options]['costs'] ||= {}
|
334
|
-
options[:options]['costs'].deep_merge!(costs)
|
335
|
-
end
|
336
|
-
options[:options]['interval'] = options[:options]['interval'].to_s.downcase if options[:options]['interval']
|
337
289
|
v_prompt = Morpheus::Cli::OptionTypes.prompt(add_budget_option_types, options[:options], @api_client)
|
338
290
|
params.deep_merge!(v_prompt)
|
339
|
-
|
291
|
+
# downcase year 'custom' always
|
292
|
+
if params['year']
|
293
|
+
params['interval'] = params['interval'].to_s.downcase
|
294
|
+
end
|
295
|
+
# downcase interval always
|
296
|
+
if params['interval']
|
297
|
+
params['interval'] = params['interval'].to_s.downcase
|
298
|
+
end
|
299
|
+
# parse MM/DD/YY but need to convert to to ISO format YYYY-MM-DD for api
|
300
|
+
standard_start_date = (params['startDate'] ? Time.strptime(params['startDate'], "%x") : nil) rescue nil
|
301
|
+
if standard_start_date
|
302
|
+
params['startDate'] = format_date(standard_start_date, {format:"%Y-%m-%d"})
|
303
|
+
end
|
304
|
+
standard_end_date = (params['endDate'] ? Time.strptime(params['endDate'], "%x") : nil) rescue nil
|
305
|
+
if standard_end_date
|
306
|
+
params['endDate'] = format_date(standard_end_date, {format:"%Y-%m-%d"})
|
307
|
+
end
|
308
|
+
if !costs.empty?
|
309
|
+
params['costs'] = costs
|
310
|
+
else
|
311
|
+
params['costs'] = prompt_costs(params, options)
|
312
|
+
end
|
340
313
|
# budgets api expects scope prefixed parameters like this
|
341
314
|
if params['tenant'].is_a?(String) || params['tenant'].is_a?(Numeric)
|
342
315
|
params['scopeTenantId'] = params.delete('tenant')
|
@@ -377,30 +350,45 @@ class Morpheus::Cli::BudgetsCommand
|
|
377
350
|
def update(args)
|
378
351
|
options = {}
|
379
352
|
params = {}
|
380
|
-
costs =
|
353
|
+
costs = []
|
381
354
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
382
355
|
opts.banner = subcommand_usage("[budget] [options]")
|
383
356
|
build_option_type_options(opts, options, update_budget_option_types)
|
384
|
-
opts.on('--cost [amount]', String, "Budget cost amount, for use with default year interval.") do |val|
|
385
|
-
|
357
|
+
# opts.on('--cost [amount]', String, "Budget cost amount, for use with default year interval.") do |val|
|
358
|
+
# costs['year'] = (val.nil? || val.empty?) ? 0 : val.to_f
|
359
|
+
# end
|
360
|
+
opts.on('--costs COSTS', String, "Budget cost amounts, one for each interval in the budget. eg. [999]") do |val|
|
361
|
+
val = val.to_s.gsub('[', '').gsub(']', '')
|
362
|
+
costs = val.to_s.split(',').collect {|it| parse_cost_amount(it) }
|
386
363
|
end
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
costs[
|
364
|
+
(1..12).each.with_index do |cost_index, i|
|
365
|
+
opts.on("--cost#{cost_index} VALUE", String, "Cost #{cost_index.to_s.capitalize} amount") do |val|
|
366
|
+
#params["cost#{cost_index.to_s}"] = parse_cost_amount(val)
|
367
|
+
costs[i] = parse_cost_amount(val)
|
391
368
|
end
|
369
|
+
opts.add_hidden_option("--cost#{cost_index}")
|
392
370
|
end
|
393
|
-
[:
|
394
|
-
|
395
|
-
|
396
|
-
costs[month.to_s] = parse_cost_amount(val)
|
371
|
+
[:q1,:q2,:q3,:q4,].each.with_index do |quarter, i|
|
372
|
+
opts.on("--#{quarter.to_s} VALUE", String, "#{quarter.to_s.capitalize} cost amount, use with quarter interval.") do |val|
|
373
|
+
costs[i] = parse_cost_amount(val)
|
397
374
|
end
|
375
|
+
opts.add_hidden_option("--#{quarter.to_s}")
|
376
|
+
end
|
377
|
+
[:january,:february,:march,:april,:may,:june,:july,:august,:september,:october,:november,:december].each_with_index do |month, i|
|
378
|
+
opts.on("--#{month.to_s} VALUE", String, "#{month.to_s.capitalize} cost amount, use with month interval.") do |val|
|
379
|
+
costs[i] = parse_cost_amount(val)
|
380
|
+
end
|
381
|
+
opts.add_hidden_option("--#{month.to_s}")
|
398
382
|
end
|
399
383
|
opts.on('--enabled [on|off]', String, "Can be used to disable a policy") do |val|
|
400
384
|
params['enabled'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s.empty?
|
401
385
|
end
|
402
|
-
|
403
|
-
opts.footer =
|
386
|
+
build_standard_update_options(opts, options)
|
387
|
+
opts.footer = <<-EOT
|
388
|
+
Update a budget.
|
389
|
+
[budget] is required. Budget ID or name
|
390
|
+
EOT
|
391
|
+
opts.footer = "Update a budget.\n[budget] is required. Budget ID or name"
|
404
392
|
end
|
405
393
|
optparse.parse!(args)
|
406
394
|
|
@@ -409,78 +397,99 @@ class Morpheus::Cli::BudgetsCommand
|
|
409
397
|
end
|
410
398
|
|
411
399
|
connect(options)
|
412
|
-
|
400
|
+
|
401
|
+
budget = find_budget_by_name_or_id(args[0])
|
402
|
+
return 1 if budget.nil?
|
413
403
|
|
414
|
-
|
415
|
-
|
404
|
+
original_year = budget['year']
|
405
|
+
original_interval = budget['interval']
|
406
|
+
original_costs = budget['costs'].is_a?(Array) ? budget['costs'] : nil
|
416
407
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
}
|
408
|
+
# construct payload
|
409
|
+
passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
|
410
|
+
payload = nil
|
411
|
+
if options[:payload]
|
412
|
+
payload = options[:payload]
|
413
|
+
payload.deep_merge!({'budget' => passed_options}) unless passed_options.empty?
|
414
|
+
else
|
415
|
+
payload = {
|
416
|
+
'budget' => {
|
427
417
|
}
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
418
|
+
}
|
419
|
+
# allow arbitrary -O options
|
420
|
+
#passed_options.delete('costs')
|
421
|
+
passed_options.delete('tenant')
|
422
|
+
passed_options.delete('group')
|
423
|
+
passed_options.delete('cloud')
|
424
|
+
passed_options.delete('user')
|
425
|
+
payload.deep_merge!({'budget' => passed_options}) unless passed_options.empty?
|
426
|
+
# prompt for options
|
427
|
+
#params = Morpheus::Cli::OptionTypes.prompt(update_budget_option_types, options[:options], @api_client, options[:params])
|
428
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(update_budget_option_types, options[:options].merge(:no_prompt => true), @api_client)
|
429
|
+
params.deep_merge!(v_prompt)
|
430
|
+
# downcase year 'custom' always
|
431
|
+
if params['year']
|
432
|
+
params['interval'] = params['interval'].to_s.downcase
|
433
|
+
end
|
434
|
+
# downcase interval always
|
435
|
+
if params['interval']
|
436
|
+
params['interval'] = params['interval'].to_s.downcase
|
437
|
+
end
|
438
|
+
# parse MM/DD/YY but need to convert to to ISO format YYYY-MM-DD for api
|
439
|
+
if params['startDate']
|
440
|
+
params['startDate'] = format_date(parse_time(params['startDate']), {format:"%Y-%m-%d"})
|
441
|
+
end
|
442
|
+
if params['endDate']
|
443
|
+
params['endDate'] = format_date(parse_time(params['endDate']), {format:"%Y-%m-%d"})
|
444
|
+
end
|
445
|
+
if !costs.empty?
|
446
|
+
params['costs'] = costs
|
447
|
+
# merge original costs in on update unless interval is changing too, should check original_year too probably if going to custom...
|
448
|
+
if params['interval'] && params['interval'] != original_interval
|
449
|
+
original_costs = nil
|
455
450
|
end
|
456
|
-
if
|
457
|
-
|
451
|
+
if original_costs
|
452
|
+
original_costs.each_with_index do |original_cost, i|
|
453
|
+
if params['costs'][i].nil?
|
454
|
+
params['costs'][i] = original_cost
|
455
|
+
end
|
456
|
+
end
|
458
457
|
end
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
458
|
+
else
|
459
|
+
if params['interval'] && params['interval'] != original_interval
|
460
|
+
raise_command_error "Changing interval requires setting the costs as well.\n#{optparse}"
|
463
461
|
end
|
464
462
|
end
|
465
|
-
|
466
|
-
if
|
467
|
-
|
468
|
-
return
|
463
|
+
# budgets api expects scope prefixed parameters like this
|
464
|
+
if params['tenant'].is_a?(String) || params['tenant'].is_a?(Numeric)
|
465
|
+
params['scopeTenantId'] = params.delete('tenant')
|
469
466
|
end
|
470
|
-
|
471
|
-
|
472
|
-
print JSON.pretty_generate(json_response)
|
473
|
-
print "\n"
|
474
|
-
else
|
475
|
-
display_name = json_response['budget'] ? json_response['budget']['name'] : ''
|
476
|
-
print_green_success "Budget #{display_name} updated"
|
477
|
-
get([json_response['budget']['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
467
|
+
if params['group'].is_a?(String) || params['group'].is_a?(Numeric)
|
468
|
+
params['scopeGroupId'] = params.delete('group')
|
478
469
|
end
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
470
|
+
if params['cloud'].is_a?(String) || params['cloud'].is_a?(Numeric)
|
471
|
+
params['scopeCloudId'] = params.delete('cloud')
|
472
|
+
end
|
473
|
+
if params['user'].is_a?(String) || params['user'].is_a?(Numeric)
|
474
|
+
params['scopeUserId'] = params.delete('user')
|
475
|
+
end
|
476
|
+
payload.deep_merge!({'budget' => params}) unless params.empty?
|
477
|
+
if payload.empty? || payload['budget'].empty?
|
478
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
479
|
+
end
|
480
|
+
end
|
481
|
+
@budgets_interface.setopts(options)
|
482
|
+
if options[:dry_run]
|
483
|
+
print_dry_run @budgets_interface.dry.update(budget['id'], payload)
|
484
|
+
return
|
485
|
+
end
|
486
|
+
json_response = @budgets_interface.update(budget['id'], payload)
|
487
|
+
render_response(json_response, options, 'budget') do
|
488
|
+
display_name = json_response['budget'] ? json_response['budget']['name'] : ''
|
489
|
+
print_green_success "Budget #{display_name} updated"
|
490
|
+
get([json_response['budget']['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
483
491
|
end
|
492
|
+
return 0, nil
|
484
493
|
end
|
485
494
|
|
486
495
|
def remove(args)
|
@@ -582,75 +591,125 @@ class Morpheus::Cli::BudgetsCommand
|
|
582
591
|
{'fieldName' => 'cloud', 'fieldLabel' => 'Cloud', 'type' => 'select', 'optionSource' => lambda {|api_client, api_params|
|
583
592
|
@options_interface.options_for_source("clouds", {})['data']
|
584
593
|
}, 'required' => true, 'dependsOnCode' => 'budget.scope:cloud', 'displayOrder' => 7},
|
585
|
-
{'fieldName' => 'year', 'fieldLabel' => 'Period', 'type' => 'text', 'required' => true, 'defaultValue' => Time.now.year, 'description' => "The period (year) the budget applies to
|
586
|
-
{'fieldName' => '
|
594
|
+
{'fieldName' => 'year', 'fieldLabel' => 'Period', 'code' => 'budget.year', 'type' => 'text', 'required' => true, 'defaultValue' => Time.now.year, 'description' => "The period (year) the budget applies, YYYY or 'custom' to enter Start Date and End Date manually", 'displayOrder' => 8},
|
595
|
+
{'fieldName' => 'startDate', 'fieldLabel' => 'Start Date', 'type' => 'text', 'required' => true, 'description' => 'The Start Date for custom period budget eg. 2021-01-01', 'dependsOnCode' => 'budget.year:custom', 'displayOrder' => 9},
|
596
|
+
{'fieldName' => 'endDate', 'fieldLabel' => 'End Date', 'type' => 'text', 'required' => true, 'description' => 'The End Date for custom period budget eg. 2023-12-31 (must be 1, 2 or 3 years from Start Date)', 'dependsOnCode' => 'budget.year:custom', 'displayOrder' => 10},
|
597
|
+
{'fieldName' => 'interval', 'fieldLabel' => 'Interval', 'type' => 'select', 'selectOptions' => [{'name'=>'Year','value'=>'year'},{'name'=>'Quarter','value'=>'quarter'},{'name'=>'Month','value'=>'month'}], 'defaultValue' => 'year', 'required' => true, 'description' => 'The budget interval, determines cost amounts: "year", "quarter" or "month"', 'displayOrder' => 11}
|
587
598
|
]
|
588
599
|
end
|
589
600
|
|
590
601
|
def update_budget_option_types
|
591
602
|
list = add_budget_option_types()
|
592
603
|
# list = list.reject {|it| ["interval"].include? it['fieldName'] }
|
593
|
-
list.each {|it|
|
594
|
-
|
604
|
+
list.each {|it|
|
605
|
+
it.delete('required')
|
606
|
+
it.delete('defaultValue')
|
607
|
+
it.delete('dependsOnCode')
|
608
|
+
}
|
595
609
|
list
|
596
610
|
end
|
597
611
|
|
598
612
|
def prompt_costs(params={}, options={})
|
613
|
+
# user did -O costs="[3.50,3.50,3.50,5.00]" so just pass through
|
614
|
+
default_costs = []
|
615
|
+
if options[:options]['costs'] && options[:options]['costs'].is_a?(Array)
|
616
|
+
default_costs = options[:options]['costs']
|
617
|
+
default_costs.each_with_index do |default_cost, i|
|
618
|
+
interval_index = i + 1
|
619
|
+
if !default_cost.nil?
|
620
|
+
options[:options]["cost#{interval_index}"] = default_cost
|
621
|
+
end
|
622
|
+
end
|
623
|
+
end
|
624
|
+
# prompt for each Period Cost based on interval [year|quarter|month]
|
625
|
+
budget_period_year = (params['year'] || params['periodValue'])
|
626
|
+
is_custom = budget_period_year == 'custom'
|
599
627
|
interval = params['interval'] #.to_s.downcase
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
#
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
628
|
+
total_years = 1
|
629
|
+
total_months = 12
|
630
|
+
costs = []
|
631
|
+
# custom timeframe so prompt from start to end by interval
|
632
|
+
start_date = nil
|
633
|
+
end_date = nil
|
634
|
+
|
635
|
+
if is_custom
|
636
|
+
start_date = parse_time(params['startDate'])
|
637
|
+
if start_date.nil?
|
638
|
+
raise_command_error "startDate is required for custom period budgets"
|
639
|
+
end
|
640
|
+
end_date = parse_time(params['endDate'])
|
641
|
+
if end_date.nil?
|
642
|
+
raise_command_error "endDate is required for custom period budgets"
|
643
|
+
end
|
608
644
|
else
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
645
|
+
budget_year = budget_period_year ? budget_period_year.to_i : Time.now.year.to_i
|
646
|
+
start_date = Time.new(budget_year, 1, 1)
|
647
|
+
end_date = Time.new(budget_year, 12, 31)
|
648
|
+
end
|
649
|
+
epoch_start_month = (start_date.year * 12) + start_date.month
|
650
|
+
epoch_end_month = (end_date.year * 12) + end_date.month
|
651
|
+
# total_months gets + 1 because endDate is same year, on last day of the month, Dec 31 by default
|
652
|
+
total_months = (epoch_end_month - epoch_start_month) + 1
|
653
|
+
total_years = (total_months / 12)
|
654
|
+
cost_option_types = []
|
655
|
+
interval_count = total_months
|
656
|
+
if interval == 'year'
|
657
|
+
interval_count = total_months / 12
|
658
|
+
elsif interval == 'quarter'
|
659
|
+
interval_count = total_months / 3
|
660
|
+
end
|
661
|
+
|
662
|
+
is_fiscal = start_date.month != 1 || start_date.day != 1
|
663
|
+
|
664
|
+
# debug budget shenanigans
|
665
|
+
# puts "START: #{start_date}"
|
666
|
+
# puts "END: #{end_date}"
|
667
|
+
# puts "EPOCH MONTHS: #{epoch_start_month} - #{epoch_end_month}"
|
668
|
+
# puts "TOTAL MONTHS: #{total_months}"
|
669
|
+
# puts "INTERVAL COUNT IS: #{interval_count}"
|
670
|
+
|
671
|
+
if total_months < 0
|
672
|
+
raise_command_error "budget cannot end (#{end_date}) before it starts (#{start_date})"
|
673
|
+
end
|
674
|
+
if (total_months % 12) != 0 || (total_months > 36)
|
675
|
+
raise_command_error "budget custom period must be 12, 24, or 36 months."
|
676
|
+
end
|
677
|
+
if interval == 'year'
|
678
|
+
(1..interval_count).each_with_index do |interval_index, i|
|
679
|
+
interval_start_month = epoch_start_month + (i * 12)
|
680
|
+
interval_date = Time.new((interval_start_month / 12), (interval_start_month % 12) == 0 ? 12 : (interval_start_month % 12), 1)
|
681
|
+
display_year = is_fiscal ? "FY #{interval_date.year + 1}" : interval_date.year.to_s
|
682
|
+
field_name = "cost#{interval_index}"
|
683
|
+
field_label = "#{display_year} Cost"
|
684
|
+
cost_option_types << {'fieldName' => field_name, 'fieldLabel' => field_label, 'type' => 'text', 'required' => true, 'defaultValue' => (default_costs[i] || 0).to_s}
|
685
|
+
end
|
686
|
+
elsif interval == 'quarter'
|
687
|
+
(1..interval_count).each_with_index do |interval_index, i|
|
688
|
+
interval_start_month = epoch_start_month + (i * 3)
|
689
|
+
interval_date = Time.new((interval_start_month / 12), (interval_start_month % 12) == 0 ? 12 : (interval_start_month % 12), 1)
|
690
|
+
interval_end_date = Time.new((interval_start_month / 12), (interval_start_month % 12) == 0 ? 12 : (interval_start_month % 12), 1)
|
691
|
+
display_year = is_fiscal ? "FY #{interval_date.year + 1}" : interval_date.year.to_s
|
692
|
+
field_name = "cost#{interval_index}"
|
693
|
+
# field_label = "Q#{interval_index} Cost"
|
694
|
+
field_label = "Q#{(i % 4) + 1} #{display_year} Cost"
|
695
|
+
cost_option_types << {'fieldName' => field_name, 'fieldLabel' => field_label, 'type' => 'text', 'required' => true, 'defaultValue' => (default_costs[i] || 0).to_s}
|
653
696
|
end
|
697
|
+
elsif interval == 'month'
|
698
|
+
(1..interval_count).each_with_index do |interval_index, i|
|
699
|
+
interval_start_month = epoch_start_month + i
|
700
|
+
interval_date = Time.new((interval_start_month / 12), (interval_start_month % 12) == 0 ? 12 : (interval_start_month % 12), 1)
|
701
|
+
display_year = is_fiscal ? "FY #{interval_date.year + 1}" : interval_date.year.to_s
|
702
|
+
field_name = "cost#{interval_index}"
|
703
|
+
# field_label = "#{interval_date.strftime('%B %Y')} Cost"
|
704
|
+
field_label = "#{interval_date.strftime('%B')} #{display_year} Cost"
|
705
|
+
cost_option_types << {'fieldName' => field_name, 'fieldLabel' => field_label, 'type' => 'text', 'required' => true, 'defaultValue' => (default_costs[i] || 0).to_s}
|
706
|
+
end
|
707
|
+
end
|
708
|
+
# values is a Hash like {"cost1": 99.0, "cost2": 55.0}
|
709
|
+
values = Morpheus::Cli::OptionTypes.prompt(cost_option_types, options[:options], @api_client)
|
710
|
+
values.each do |k,v|
|
711
|
+
interval_index = k[4..-1].to_i
|
712
|
+
costs[interval_index-1] = parse_cost_amount(v).to_f
|
654
713
|
end
|
655
714
|
return costs
|
656
715
|
end
|
@@ -665,8 +724,19 @@ class Morpheus::Cli::BudgetsCommand
|
|
665
724
|
end
|
666
725
|
end
|
667
726
|
|
727
|
+
# convert String like "$5,499.99" to Float 5499.99
|
668
728
|
def parse_cost_amount(val)
|
669
|
-
val.to_s.gsub(",","").to_f
|
729
|
+
val.to_s.gsub(",","").gsub('$','').strip.to_f
|
730
|
+
end
|
731
|
+
|
732
|
+
def format_budget_interval_label(budget, budget_interval)
|
733
|
+
label = ""
|
734
|
+
if budget_interval['chartName']
|
735
|
+
label = budget_interval['chartName']
|
736
|
+
else
|
737
|
+
label = (budget_interval['shortName'] || budget_interval['shortYear']).to_s
|
738
|
+
end
|
739
|
+
return label
|
670
740
|
end
|
671
741
|
|
672
742
|
end
|