morpheus-cli 5.2.1 → 5.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4409a856333a1b2957649aac67f62677b943706754ebc810c614fa5f879b120d
4
- data.tar.gz: bb968663da852f4ea631fc9b0c89c115f6fb1067049585953c33b7c24e4b284a
3
+ metadata.gz: 91687d55f353976028bdebb05b8b588fc56ae1d590694da9948115098b05fb5f
4
+ data.tar.gz: 370d606fe6b918dcc18a94f191fc9706bba0aef4832a3ec1aa24da8fc8d3377a
5
5
  SHA512:
6
- metadata.gz: 2be204c65dd3c2a35ce5ac7170e70a2297aa34ee1348db2fc736a5915a2678c0c45361269a801db228f202e27dcdcfaaf69afc7f49d9eded429c16d5381dd28f
7
- data.tar.gz: a5018a327ae43078e4661c1c3744e75b6cc1adfc8854ca09d99a0a47f8e0678d2f8914b46d724bd8189011b13a42f2ac5779b3980611af1655cdebc4393a3e53
6
+ metadata.gz: dba600f78525d9176052b7a6319db0c2131c4e1cbf140eea951613ff5920b3b7be3b2cf597a9dfa77c512336a936122c1b2cfc2c0d4436ee857c016ecf6da200
7
+ data.tar.gz: 7cc0c47c12664abe7f012dd6520762fcbb8be6a9cc95a75d81688e7f3340bdcf8fbdd73c7347d3fc741bcc67853eaf42fe490ed097ba69558f76a1482d702fb9
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.5.1
2
2
 
3
- RUN gem install morpheus-cli -v 5.2.0
3
+ RUN gem install morpheus-cli -v 5.2.2
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -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: "/api/invoices", headers: {params: params})
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: "/api/invoices/#{id}", headers: {params: params})
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: "/api/invoices/refresh", headers: headers, payload: payload.to_json)
25
+ execute(method: :post, url: "#{base_path}/refresh", headers: headers, payload: payload.to_json)
17
26
  end
18
27
 
19
28
  end
@@ -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
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
25
+ build_standard_list_options(opts, options)
25
26
  opts.footer = "List budgets."
26
27
  end
27
28
  optparse.parse!(args)
28
- if args.count != 0
29
- raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
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
- begin
33
- params.merge!(parse_list_options(options))
34
- @budgets_interface.setopts(options)
35
- if options[:dry_run]
36
- print_dry_run @budgets_interface.dry.list(params)
37
- return 0
38
- end
39
- json_response = @budgets_interface.list(params)
40
- render_result = render_with_format(json_response, options, 'budgets')
41
- return 0 if render_result
42
- budgets = json_response['budgets']
43
- unless options[:quiet]
44
- title = "Morpheus Budgets"
45
- subtitles = []
46
- subtitles += parse_list_subtitles(options)
47
- print_h1 title, subtitles
48
- if budgets.empty?
49
- print cyan,"No budgets found.",reset,"\n"
50
- else
51
- columns = [
52
- {"ID" => lambda {|budget| budget['id'] } },
53
- {"NAME" => lambda {|budget| budget['name'] } },
54
- {"DESCRIPTION" => lambda {|budget| truncate_string(budget['description'], 30) } },
55
- # {"ENABLED" => lambda {|budget| format_boolean(budget['enabled']) } },
56
- # {"SCOPE" => lambda {|it| format_budget_scope(it) } },
57
- {"SCOPE" => lambda {|it| it['refName'] } },
58
- {"PERIOD" => lambda {|it| it['year'] } },
59
- {"INTERVAL" => lambda {|it| it['interval'].to_s.capitalize } },
60
- # the UI doesn't consider timezone, so uhh do it this hacky way for now.
61
- {"START DATE" => lambda {|it|
62
- if it['timezone'] == 'UTC'
63
- ((parse_time(it['startDate'], "%Y-%m-%d").strftime("%x")) rescue it['startDate']) # + ' UTC'
64
- else
65
- format_local_date(it['startDate'])
66
- end
67
- } },
68
- {"END DATE" => lambda {|it|
69
- if it['timezone'] == 'UTC'
70
- ((parse_time(it['endDate'], "%Y-%m-%d").strftime("%x")) rescue it['endDate']) # + ' UTC'
71
- else
72
- format_local_date(it['endDate'])
73
- end
74
- } },
75
- {"TOTAL" => lambda {|it| format_money(it['totalCost'], it['currency']) } },
76
- {"AVERAGE" => lambda {|it| format_money(it['averageCost'], it['currency']) } },
77
- # {"CREATED BY" => lambda {|budget| budget['createdByName'] ? budget['createdByName'] : budget['createdById'] } },
78
- # {"CREATED" => lambda {|budget| format_local_dt(budget['dateCreated']) } },
79
- # {"UPDATED" => lambda {|budget| format_local_dt(budget['lastUpdated']) } },
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 reset,"\n"
85
+ print as_pretty_table(budgets, columns, options)
86
+ print_results_pagination(json_response)
88
87
  end
89
- return 0
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
- build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
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
- begin
112
- @budgets_interface.setopts(options)
113
- if options[:dry_run]
114
- if args[0].to_s =~ /\A\d{1,}\Z/
115
- print_dry_run @budgets_interface.dry.get(args[0], params)
116
- else
117
- print_dry_run @budgets_interface.dry.list({name: args[0].to_s})
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
- render_result = render_with_format(json_response, options, 'budget')
130
- return 0 if render_result
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
- "Timezone" => lambda {|it| it['timezone'] },
205
- })
206
- budget_columns.merge!({
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
- # budget['stats']['intervals'].each do |stat_interval|
225
- # interval_key = (stat_interval["shortName"] || stat_interval["shortYear"]).to_s.upcase
226
- # if interval_key == "Y1" && budget['year']
227
- # interval_key = "Year #{budget['year']}"
228
- # end
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 = (stat_interval["shortName"] || stat_interval["shortYear"]).to_s.upcase
244
- if interval_key == "Y1" && budget['year']
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 = stat_interval["budget"].to_f
250
- actual_cost = stat_interval["cost"].to_f
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
- costs['year'] = (val.nil? || val.empty?) ? 0 : val.to_f
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
- ].each do |quarter|
288
- opts.on("--#{quarter.to_s} [amount]", String, "#{quarter.to_s.capitalize} cost amount, use with quarter interval.") do |val|
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
- ].each do |month|
294
- opts.on("--#{month.to_s} [amount]", String, "#{month.to_s.capitalize} cost amount, use with month interval.") do |val|
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
- build_common_options(opts, options, [:payload, :options, :json, :dry_run, :remote])
302
- opts.footer = "Create budget."
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
- params['costs'] = prompt_costs(params, options)
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
- costs['year'] = (val.nil? || val.empty?) ? 0 : val.to_f
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
- [:q1,:q2,:q3,:q4,
388
- ].each do |quarter|
389
- opts.on("--#{quarter.to_s} [amount]", String, "#{quarter.to_s.capitalize} cost amount, use with quarter interval.") do |val|
390
- costs[quarter.to_s] = parse_cost_amount(val)
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
- [:january,:february,:march,:april,:may,:june,:july,:august,:september,:october,:november,:december
394
- ].each do |month|
395
- opts.on("--#{month.to_s} [amount]", String, "#{month.to_s.capitalize} cost amount, use with month interval.") do |val|
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
- build_common_options(opts, options, [:payload, :options, :json, :dry_run, :remote])
403
- opts.footer = "Update budget.\n[budget] is required. Budget ID or name"
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
- begin
400
+
401
+ budget = find_budget_by_name_or_id(args[0])
402
+ return 1 if budget.nil?
413
403
 
414
- budget = find_budget_by_name_or_id(args[0])
415
- return 1 if budget.nil?
404
+ original_year = budget['year']
405
+ original_interval = budget['interval']
406
+ original_costs = budget['costs'].is_a?(Array) ? budget['costs'] : nil
416
407
 
417
- # construct payload
418
- passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
419
- payload = nil
420
- if options[:payload]
421
- payload = options[:payload]
422
- payload.deep_merge!({'budget' => passed_options}) unless passed_options.empty?
423
- else
424
- payload = {
425
- 'budget' => {
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
- # allow arbitrary -O options
429
- passed_options.delete('costs')
430
- passed_options.delete('tenant')
431
- passed_options.delete('group')
432
- passed_options.delete('cloud')
433
- passed_options.delete('user')
434
- payload.deep_merge!({'budget' => passed_options}) unless passed_options.empty?
435
- # prompt for options
436
- #params = Morpheus::Cli::OptionTypes.prompt(update_budget_option_types, options[:options], @api_client, options[:params])
437
- v_prompt = Morpheus::Cli::OptionTypes.prompt(update_budget_option_types, options[:options].merge(:no_prompt => true), @api_client)
438
- params.deep_merge!(v_prompt)
439
- # v_costs = prompt_costs({'interval' => budget['interval']}.merge(params), options.merge(:no_prompt => true))
440
- # if v_costs && !v_costs.empty?
441
- # params['costs'] = v_costs
442
- # end
443
- if !costs.empty?
444
- params['costs'] = costs
445
- end
446
- # budgets api expects scope prefixed parameters like this
447
- if params['tenant'].is_a?(String) || params['tenant'].is_a?(Numeric)
448
- params['scopeTenantId'] = params.delete('tenant')
449
- end
450
- if params['group'].is_a?(String) || params['group'].is_a?(Numeric)
451
- params['scopeGroupId'] = params.delete('group')
452
- end
453
- if params['cloud'].is_a?(String) || params['cloud'].is_a?(Numeric)
454
- params['scopeCloudId'] = params.delete('cloud')
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 params['user'].is_a?(String) || params['user'].is_a?(Numeric)
457
- params['scopeUserId'] = params.delete('user')
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
- payload.deep_merge!({'budget' => params}) unless params.empty?
460
-
461
- if payload.empty? || payload['budget'].empty?
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
- @budgets_interface.setopts(options)
466
- if options[:dry_run]
467
- print_dry_run @budgets_interface.dry.update(budget['id'], payload)
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
- json_response = @budgets_interface.update(budget['id'], payload)
471
- if options[:json]
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
- return 0
480
- rescue RestClient::Exception => e
481
- print_rest_exception(e, options)
482
- exit 1
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. Default is the current year.", 'displayOrder' => 8},
586
- {'fieldName' => 'interval', 'fieldLabel' => 'Interval', 'type' => 'select', 'selectOptions' => [{'name'=>'Year','value'=>'year'},{'name'=>'Quarter','value'=>'quarter'},{'name'=>'Month','value'=>'month'}], 'defaultValue' => 'year', 'required' => true, 'displayOrder' => 9}
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| it.delete('required') }
594
- list.each {|it| it.delete('defaultValue') }
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
- options[:options]||={}
601
- costs = {}
602
- costs_val = nil
603
- #costs_val = params['costs'] ? params['costs'] : options[:options]['costs']
604
- if costs_val.is_a?(Array)
605
- costs = costs_val
606
- elsif costs_val.is_a?(String)
607
- costs = costs_val.to_s.split(',').collect {|it| it.to_s.strip.to_f }
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
- if interval == 'year'
610
- cost_option_types = [
611
- {'fieldContext' => 'costs', 'fieldName' => 'year', 'fieldLabel' => 'Annual Cost', 'type' => 'text', 'defaultValue' => 0}
612
- ]
613
- values = Morpheus::Cli::OptionTypes.prompt(cost_option_types, options[:options], @api_client)
614
- costs = values['costs'] ? values['costs'] : {}
615
- # costs = {
616
- # year: values['cost']
617
- # }
618
- elsif interval == 'quarter'
619
- cost_option_types = [
620
- {'fieldContext' => 'costs', 'fieldName' => 'q1', 'fieldLabel' => 'Q1', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 1},
621
- {'fieldContext' => 'costs', 'fieldName' => 'q2', 'fieldLabel' => 'Q2', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 2},
622
- {'fieldContext' => 'costs', 'fieldName' => 'q3', 'fieldLabel' => 'Q3', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 3},
623
- {'fieldContext' => 'costs', 'fieldName' => 'q4', 'fieldLabel' => 'Q4', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 4}
624
- ]
625
- values = Morpheus::Cli::OptionTypes.prompt(cost_option_types, options[:options], @api_client)
626
- costs = values['costs'] ? values['costs'] : {}
627
- # costs = {
628
- # q1: values['q1'], q2: values['q2'], q3: values['q3'], q4: values['q4']
629
- # }
630
- elsif interval == 'month'
631
- cost_option_types = [
632
- {'fieldContext' => 'costs', 'fieldName' => 'january', 'fieldLabel' => 'January', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 1},
633
- {'fieldContext' => 'costs', 'fieldName' => 'february', 'fieldLabel' => 'February', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 2},
634
- {'fieldContext' => 'costs', 'fieldName' => 'march', 'fieldLabel' => 'March', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 3},
635
- {'fieldContext' => 'costs', 'fieldName' => 'april', 'fieldLabel' => 'April', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 4},
636
- {'fieldContext' => 'costs', 'fieldName' => 'may', 'fieldLabel' => 'May', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 5},
637
- {'fieldContext' => 'costs', 'fieldName' => 'june', 'fieldLabel' => 'June', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 6},
638
- {'fieldContext' => 'costs', 'fieldName' => 'july', 'fieldLabel' => 'July', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 7},
639
- {'fieldContext' => 'costs', 'fieldName' => 'august', 'fieldLabel' => 'August', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 8},
640
- {'fieldContext' => 'costs', 'fieldName' => 'september', 'fieldLabel' => 'September', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 9},
641
- {'fieldContext' => 'costs', 'fieldName' => 'october', 'fieldLabel' => 'October', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 10},
642
- {'fieldContext' => 'costs', 'fieldName' => 'november', 'fieldLabel' => 'November', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 11},
643
- {'fieldContext' => 'costs', 'fieldName' => 'december', 'fieldLabel' => 'December', 'type' => 'text', 'defaultValue' => 0, 'displayOrder' => 12},
644
- ]
645
- values = Morpheus::Cli::OptionTypes.prompt(cost_option_types, options[:options], @api_client)
646
- costs = values['costs'] ? values['costs'] : {}
647
- # costs = {
648
- # january: values['january'], february: values['february'], march: values['march'],
649
- # april: values['april'], may: values['may'], june: values['june'],
650
- # july: values['july'], august: values['august'], september: values['september'],
651
- # october: values['october'], november: values['november'], december: values['december']
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