pal_tool 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +132 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Dockerfile +10 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +72 -0
- data/LICENSE.txt +21 -0
- data/README.md +124 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/pal +47 -0
- data/lib/pal/common/local_file_utils.rb +37 -0
- data/lib/pal/common/object_helpers.rb +27 -0
- data/lib/pal/common/safe_hash_parse.rb +87 -0
- data/lib/pal/configuration.rb +77 -0
- data/lib/pal/handler/base.rb +138 -0
- data/lib/pal/handler/definitions/aws_cur.json +8 -0
- data/lib/pal/handler/manager.rb +30 -0
- data/lib/pal/handler/processor.rb +84 -0
- data/lib/pal/log.rb +29 -0
- data/lib/pal/main.rb +63 -0
- data/lib/pal/operation/actions.rb +106 -0
- data/lib/pal/operation/exporter.rb +183 -0
- data/lib/pal/operation/filter_evaluator.rb +249 -0
- data/lib/pal/operation/processor_context.rb +50 -0
- data/lib/pal/operation/projection.rb +302 -0
- data/lib/pal/plugin.rb +61 -0
- data/lib/pal/request/metadata.rb +19 -0
- data/lib/pal/request/runbook.rb +54 -0
- data/lib/pal/version.rb +5 -0
- data/lib/pal.rb +43 -0
- data/plugins/PLUGINS.md +1 -0
- data/plugins/operation/terminal_exporter_impl.rb +14 -0
- data/templates/DOCUMENTATION.md +46 -0
- data/templates/aws/data_transfer/data_transfer_breakdown.json +93 -0
- data/templates/aws/ec2/ec2_compute_hourly_breakdown.json +63 -0
- data/templates/aws/ec2/ec2_operation_breakdown.json +64 -0
- data/templates/aws/ec2/ec2_spend_breakdown.json +63 -0
- data/templates/aws/global_resource_and_usage_type_costs.json +41 -0
- data/templates/aws/kms/kms_usage_counts.json +52 -0
- data/templates/aws/kms/kms_usage_list.json +80 -0
- data/templates/aws/kms/list_of_kms_keys.json +57 -0
- data/templates/aws/reserved_instances/all_reserved_instance_expiries.json +41 -0
- data/templates/aws/reserved_instances/reserved_instance_opportunities.json +60 -0
- data/templates/aws/summary_cost_between_date_range.json +43 -0
- data/templates/aws/summary_daily_breakdown_costs.json +39 -0
- data/templates/azure/global_resource_type_summary.json +47 -0
- metadata +136 -0
@@ -0,0 +1,302 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pal"
|
4
|
+
|
5
|
+
module Pal
|
6
|
+
module Operation
|
7
|
+
class Projection
|
8
|
+
include Log
|
9
|
+
|
10
|
+
# @return [String]
|
11
|
+
attr_reader :type
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
attr_reader :property
|
15
|
+
|
16
|
+
# @param [String] type
|
17
|
+
# @param [String] property
|
18
|
+
def initialize(type, property)
|
19
|
+
@type = type
|
20
|
+
@property = property
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [Array<String>] group_by_rules # export columns
|
24
|
+
# @param [Hash] groups
|
25
|
+
# @param [Hash] column_headers
|
26
|
+
# @return [Array] rows, column_headers
|
27
|
+
def process(group_by_rules, groups, column_headers)
|
28
|
+
log_info("Calling down to projection impl [#{type}]")
|
29
|
+
_process_impl(group_by_rules, groups, column_headers)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Boolean]
|
33
|
+
def processable?
|
34
|
+
!(@type.nil? || @property.nil?)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @abstract
|
40
|
+
# @param [Array<String>] _group_by_rules
|
41
|
+
# @param [Hash] _groups
|
42
|
+
# @param [Hash] _column_headers
|
43
|
+
# @return [Array] rows, column_headers
|
44
|
+
def _process_impl(_group_by_rules, _groups, _column_headers)
|
45
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class SumProjectionImpl < Projection
|
50
|
+
# @param [String] property
|
51
|
+
def initialize(property)
|
52
|
+
super("sum", property)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# @param [Array<String>] group_by_rules
|
58
|
+
# @param [Hash] groups
|
59
|
+
# @param [Hash] column_headers
|
60
|
+
# @return [Array] rows, column_headers
|
61
|
+
# rubocop:disable Metrics/AbcSize
|
62
|
+
# rubocop:disable Metrics/MethodLength
|
63
|
+
def _process_impl(group_by_rules, groups, column_headers)
|
64
|
+
rows = []
|
65
|
+
sum_column_idx = column_headers[@property]
|
66
|
+
|
67
|
+
raise "Missing column. Please include [#{@property}] in columns #{column_headers.keys}." unless sum_column_idx
|
68
|
+
|
69
|
+
groups.each_key do |key|
|
70
|
+
sum = 0.0
|
71
|
+
|
72
|
+
row = groups[key]
|
73
|
+
|
74
|
+
row.each do |entry|
|
75
|
+
sum += entry[sum_column_idx].to_f
|
76
|
+
end
|
77
|
+
|
78
|
+
arr = []
|
79
|
+
group_by_rules.each do |gb|
|
80
|
+
idx = column_headers[gb]
|
81
|
+
arr << row[0][idx]
|
82
|
+
end
|
83
|
+
|
84
|
+
arr << sum.round(8)
|
85
|
+
rows << arr
|
86
|
+
end
|
87
|
+
|
88
|
+
column_headers = {}
|
89
|
+
group_by_rules.each_with_index { |gb, idx| column_headers[gb] = idx }
|
90
|
+
column_headers["sum_#{@property}"] = group_by_rules.size
|
91
|
+
|
92
|
+
[rows, column_headers]
|
93
|
+
end
|
94
|
+
# rubocop:enable Metrics/MethodLength
|
95
|
+
# rubocop:enable Metrics/AbcSize
|
96
|
+
end
|
97
|
+
|
98
|
+
class DistinctProjectionImpl < Projection
|
99
|
+
# @param [String] property
|
100
|
+
def initialize(property)
|
101
|
+
super("distinct", property)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# @param [Array<String>] _group_by_rules
|
107
|
+
# @param [Hash] groups
|
108
|
+
# @param [Hash] column_headers
|
109
|
+
# @return [Array] rows, column_headers
|
110
|
+
def _process_impl(_group_by_rules, groups, column_headers)
|
111
|
+
rows = []
|
112
|
+
distinct_column_idx = column_headers[@property]
|
113
|
+
|
114
|
+
raise "Missing column index. Please include [#{@property}] in column extraction." unless distinct_column_idx
|
115
|
+
|
116
|
+
groups.each_key do |key|
|
117
|
+
row = groups[key]
|
118
|
+
|
119
|
+
row.each do |entry|
|
120
|
+
prop = entry[distinct_column_idx]
|
121
|
+
rows << prop unless rows.include?(prop)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
column_headers = {}
|
126
|
+
column_headers["distinct_#{@property}"] = 0
|
127
|
+
|
128
|
+
[rows.map { |x| [x] }, column_headers]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class MaxMinProjectionImpl < Projection
|
133
|
+
private
|
134
|
+
|
135
|
+
# @param [Array<String>] _group_by_rules
|
136
|
+
# @param [Hash] groups
|
137
|
+
# @param [Hash] column_headers
|
138
|
+
# @return [Array] rows, column_headers
|
139
|
+
# rubocop:disable Metrics/AbcSize
|
140
|
+
def _process_impl(_group_by_rules, groups, column_headers)
|
141
|
+
max_vals = {}
|
142
|
+
max_column_idx = column_headers[@property]
|
143
|
+
|
144
|
+
groups.each_key do |key|
|
145
|
+
row = groups[key]
|
146
|
+
|
147
|
+
row.each do |entry|
|
148
|
+
prop_val = entry[max_column_idx].to_f
|
149
|
+
|
150
|
+
max_vals[key] = entry unless max_vals.key?(key)
|
151
|
+
max_vals[key] = entry if _comparator_proc.call(max_vals[key][0][max_column_idx].to_f, prop_val)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
rows = max_vals.values
|
156
|
+
|
157
|
+
new_column_headers = {}
|
158
|
+
column_headers.keys.each_with_index { |ch, idx| new_column_headers[ch] = idx }
|
159
|
+
|
160
|
+
[rows, new_column_headers]
|
161
|
+
end
|
162
|
+
# rubocop:enable Metrics/AbcSize
|
163
|
+
#
|
164
|
+
|
165
|
+
def _comparator_proc
|
166
|
+
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class MaxProjectionImpl < MaxMinProjectionImpl
|
171
|
+
# @param [String] property
|
172
|
+
def initialize(property)
|
173
|
+
super("max", property)
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def _comparator_proc
|
179
|
+
proc { |x, y| x < y }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class MinProjectionImpl < MaxMinProjectionImpl
|
184
|
+
# @param [String] property
|
185
|
+
def initialize(property)
|
186
|
+
super("min", property)
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def _comparator_proc
|
192
|
+
proc { |x, y| x > y }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
class DefaultProjectionImpl < Projection
|
197
|
+
# @param [String] property
|
198
|
+
def initialize(property)
|
199
|
+
super("default", property)
|
200
|
+
end
|
201
|
+
|
202
|
+
# @param [Array<String>] _group_by_rules
|
203
|
+
# @param [Hash] groups
|
204
|
+
# @param [Hash] column_headers
|
205
|
+
# @return [Array] rows, column_headers
|
206
|
+
def _process_impl(_group_by_rules, groups, column_headers)
|
207
|
+
[groups.values, column_headers]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class AverageProjectionImpl < Projection
|
212
|
+
# @param [String] property
|
213
|
+
def initialize(property)
|
214
|
+
super("average", property)
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
# @param [Array<String>] group_by_rules
|
220
|
+
# @param [Hash] groups
|
221
|
+
# @param [Hash] column_headers
|
222
|
+
# @return [Array] rows, column_headers
|
223
|
+
# rubocop:disable Metrics/AbcSize
|
224
|
+
# rubocop:disable Metrics/MethodLength
|
225
|
+
def _process_impl(group_by_rules, groups, column_headers)
|
226
|
+
rows = []
|
227
|
+
sum_column_idx = column_headers[@property]
|
228
|
+
|
229
|
+
groups.each_key do |key|
|
230
|
+
sum = 0.0
|
231
|
+
|
232
|
+
records = groups[key]
|
233
|
+
records.each do |entry|
|
234
|
+
sum += entry[sum_column_idx].to_f
|
235
|
+
end
|
236
|
+
|
237
|
+
arr = []
|
238
|
+
group_by_rules.each do |gb|
|
239
|
+
next if gb.eql? @property
|
240
|
+
|
241
|
+
idx = column_headers[gb]
|
242
|
+
arr << records[0][idx]
|
243
|
+
end
|
244
|
+
|
245
|
+
arr << sum.round(8) / records.size
|
246
|
+
rows << arr
|
247
|
+
end
|
248
|
+
|
249
|
+
column_headers = {}
|
250
|
+
group_by_rules.each_with_index do |gb, idx|
|
251
|
+
next if gb.eql? @property
|
252
|
+
|
253
|
+
column_headers[gb] = idx
|
254
|
+
end
|
255
|
+
|
256
|
+
column_headers["average_#{@property}"] = group_by_rules.size - 1 # remove default
|
257
|
+
|
258
|
+
[rows, column_headers]
|
259
|
+
end
|
260
|
+
# rubocop:enable Metrics/AbcSize
|
261
|
+
# rubocop:enable Metrics/MethodLength
|
262
|
+
end
|
263
|
+
|
264
|
+
class CountProjectionImpl < Projection
|
265
|
+
# @param [String] property
|
266
|
+
def initialize(property)
|
267
|
+
super("count", property)
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
# @param [Array<String>] _group_by_rules
|
273
|
+
# @param [Hash] groups
|
274
|
+
# @param [Hash] column_headers
|
275
|
+
# @return [Array] rows, column_headers
|
276
|
+
# rubocop:disable Metrics/AbcSize
|
277
|
+
def _process_impl(_group_by_rules, groups, column_headers)
|
278
|
+
distinct_column_idx = column_headers[@property]
|
279
|
+
raise "Missing column index. Please include [#{@property}] in column extraction." unless distinct_column_idx
|
280
|
+
|
281
|
+
count_map = {}
|
282
|
+
|
283
|
+
groups.each_key do |key|
|
284
|
+
groups[key].each do |entry|
|
285
|
+
prop = entry[distinct_column_idx]
|
286
|
+
|
287
|
+
count_map[prop] = 0 unless count_map[prop]
|
288
|
+
count_map[prop] += 1
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
column_headers = {}
|
293
|
+
column_headers[@property] = 0
|
294
|
+
column_headers["count_#{@property}"] = 0
|
295
|
+
|
296
|
+
[count_map.map { |k, v| [k, v] }, column_headers]
|
297
|
+
end
|
298
|
+
# rubocop:enable Metrics/AbcSize
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
data/lib/pal/plugin.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pal"
|
4
|
+
require "pal/log"
|
5
|
+
|
6
|
+
module Pal
|
7
|
+
module Plugin
|
8
|
+
include Log
|
9
|
+
|
10
|
+
def register_plugins(plugin_dir="plugins")
|
11
|
+
log_info "Registering any plugins for directory [#{plugin_dir}]"
|
12
|
+
PluginManager.instance.register(plugin_dir)
|
13
|
+
end
|
14
|
+
|
15
|
+
class PluginManager
|
16
|
+
include Singleton
|
17
|
+
include Log
|
18
|
+
|
19
|
+
def register(plugin_dir)
|
20
|
+
candidates = Dir.glob("#{plugin_dir}/**/*.rb").select { |e| File.file? e }
|
21
|
+
|
22
|
+
log_info "Found a total of [#{candidates.size}] candidates"
|
23
|
+
candidates.each do |file_path|
|
24
|
+
full_clazz_name = get_clazz_name(file_path)
|
25
|
+
|
26
|
+
next unless defined?(full_clazz_name)
|
27
|
+
next unless full_clazz_name.start_with?("Pal::")
|
28
|
+
|
29
|
+
log_info "[#{full_clazz_name}] has passed validation and will be loaded"
|
30
|
+
|
31
|
+
load file_path
|
32
|
+
|
33
|
+
validate_plugin(full_clazz_name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def get_clazz_name(file_path)
|
40
|
+
mod, clazz = file_path.split("/")[-2..]
|
41
|
+
full_clazz = clazz.split("_").map(&:capitalize).join("").gsub(".rb", "")
|
42
|
+
"Pal::#{mod.capitalize}::#{full_clazz}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [String] full_clazz_name
|
46
|
+
# @raise [RuntimeError]
|
47
|
+
def validate_plugin(full_clazz_name)
|
48
|
+
clazz_ins = Kernel.const_get(full_clazz_name)
|
49
|
+
ancestors = clazz_ins.ancestors
|
50
|
+
|
51
|
+
valid_candidates = [Pal::Operation::BaseExportHandler]
|
52
|
+
unless valid_candidates.find { |a| ancestors.include?(a) }
|
53
|
+
log_error("Invalid plugin has been given. Valid plugin candidates are: #{valid_candidates.inspect}")
|
54
|
+
raise "Invalid plugin given!"
|
55
|
+
end
|
56
|
+
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pal
|
4
|
+
module Request
|
5
|
+
class Metadata
|
6
|
+
include Pal::ObjectHelpers
|
7
|
+
|
8
|
+
# @return [String]
|
9
|
+
attr_accessor :version, :name, :description, :handler
|
10
|
+
|
11
|
+
def initialize(opts={})
|
12
|
+
@description = opts["description"]
|
13
|
+
@version = opts["version"]
|
14
|
+
@handler = opts["handler"]
|
15
|
+
@name = opts["name"]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pal/operation/filter_evaluator"
|
4
|
+
require "pal/operation/exporter"
|
5
|
+
require "pal/operation/actions"
|
6
|
+
require "pal/request/metadata"
|
7
|
+
require "pal/common/object_helpers"
|
8
|
+
require "json"
|
9
|
+
|
10
|
+
module Pal
|
11
|
+
module Request
|
12
|
+
class Runbook
|
13
|
+
include ObjectHelpers
|
14
|
+
|
15
|
+
# @return [Pal::Request::Metadata]
|
16
|
+
attr_reader :metadata
|
17
|
+
|
18
|
+
# @return [Pal::Operation::FilterEvaluator]
|
19
|
+
attr_reader :filters
|
20
|
+
|
21
|
+
# @return [Pal::Operation::Exporter]
|
22
|
+
attr_reader :exporter
|
23
|
+
|
24
|
+
# @return [Pal::Operation::Actions]
|
25
|
+
attr_reader :actions
|
26
|
+
|
27
|
+
# @return [Pal::Operation::Transforms]
|
28
|
+
attr_reader :transforms
|
29
|
+
|
30
|
+
# @return [Hash]
|
31
|
+
attr_accessor :column_overrides
|
32
|
+
|
33
|
+
# @param [Array<Hash>] opts
|
34
|
+
def filters=(opts)
|
35
|
+
@filters = Pal::Operation::FilterEvaluator.new(opts)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [Hash] opts
|
39
|
+
def metadata=(opts)
|
40
|
+
@metadata = Pal::Request::Metadata.new(opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [Hash] opts
|
44
|
+
def exporter=(opts)
|
45
|
+
@exporter = Pal::Operation::Exporter.new.from_hash(opts)
|
46
|
+
end
|
47
|
+
|
48
|
+
# # @param [Hash] opts
|
49
|
+
# def transforms=(opts)
|
50
|
+
# @transforms = Pal::Operation::Transforms.new(opts)
|
51
|
+
# end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/pal/version.rb
ADDED
data/lib/pal.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pal/version"
|
4
|
+
require "pal/main"
|
5
|
+
require "pal/configuration"
|
6
|
+
require "pal/log"
|
7
|
+
|
8
|
+
require "pal/operation/filter_evaluator"
|
9
|
+
require "pal/handler/processor"
|
10
|
+
require "pal/operation/exporter"
|
11
|
+
require "pal/operation/actions"
|
12
|
+
|
13
|
+
require "pal/request/runbook"
|
14
|
+
require "pal/handler/manager"
|
15
|
+
|
16
|
+
require "logger"
|
17
|
+
|
18
|
+
# Entry point for Pal services
|
19
|
+
module Pal
|
20
|
+
class << self
|
21
|
+
attr_writer :logger
|
22
|
+
|
23
|
+
def logger
|
24
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
25
|
+
log.progname = name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Exception classes
|
31
|
+
class ValidationError < StandardError
|
32
|
+
attr_reader :errors
|
33
|
+
|
34
|
+
def initialize(errors, msg="Invalid Request")
|
35
|
+
super(msg)
|
36
|
+
@errors = errors
|
37
|
+
end
|
38
|
+
|
39
|
+
def message
|
40
|
+
"Validation error: [#{@errors.join(", ")}]"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/plugins/PLUGINS.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
## Plugins Documentation
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pal"
|
4
|
+
require "pal/operation/exporter"
|
5
|
+
|
6
|
+
module Pal
|
7
|
+
module Operation
|
8
|
+
class TerminalExporterImpl < BaseExportHandler
|
9
|
+
def _export(rows, _column_headers)
|
10
|
+
puts "Inside plugin! You passed me [#{rows.size}] rows"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
Documentation for Templates
|
2
|
+
===========================
|
3
|
+
|
4
|
+
Templates are made up of three parts - metadata, filters and exporter. At is most simplest, only the metadata field needs to be provided.
|
5
|
+
|
6
|
+
##Metadata
|
7
|
+
```json
|
8
|
+
"metadata" : {
|
9
|
+
"version" : "2022-04-02",
|
10
|
+
"name" : "<name>",
|
11
|
+
"description" : "<description>"
|
12
|
+
"handler" : "<handler>"
|
13
|
+
},...
|
14
|
+
```
|
15
|
+
|
16
|
+
###Keys
|
17
|
+
- **version**: API version, default is [2022-04-02]
|
18
|
+
- **name**: Name of the template.
|
19
|
+
- **description**: Description of the template.
|
20
|
+
- **handler**: Specifies the type of spreadsheet to process. Default is [AwsCur]
|
21
|
+
|
22
|
+
-------------------------------------------
|
23
|
+
|
24
|
+
##Filters
|
25
|
+
```json
|
26
|
+
...
|
27
|
+
"filters": {
|
28
|
+
"condition": "<and/or>",
|
29
|
+
"rules": [{
|
30
|
+
"field": "<column_header>",
|
31
|
+
"type": "<number|string>",
|
32
|
+
"operator": "<operator>",
|
33
|
+
"value": "<value>"
|
34
|
+
}
|
35
|
+
]
|
36
|
+
},...
|
37
|
+
```
|
38
|
+
###Keys
|
39
|
+
- **condition**: Must be either and/or. Can be nested.
|
40
|
+
- **rules**: List of rules
|
41
|
+
- **field**: Field found in spreadsheet.
|
42
|
+
- **type**: Data type, either string or number.
|
43
|
+
- **operator**: Predicate to validate against - <equal, not_equal,..>.
|
44
|
+
- **value**: Value to validate against.
|
45
|
+
|
46
|
+
|
@@ -0,0 +1,93 @@
|
|
1
|
+
{
|
2
|
+
"metadata" : {
|
3
|
+
"version" : "2022-04-02",
|
4
|
+
"name" : "Data Transfer Breakdown",
|
5
|
+
"handler" : "AwsCur",
|
6
|
+
"description" : "Spend breakdown for data transfer (From/To)"
|
7
|
+
},
|
8
|
+
"filters": {
|
9
|
+
"condition": "AND",
|
10
|
+
"rules": [
|
11
|
+
{
|
12
|
+
"field": "product/productFamily",
|
13
|
+
"type": "string",
|
14
|
+
"operator": "equal",
|
15
|
+
"value": "Data Transfer"
|
16
|
+
},
|
17
|
+
{
|
18
|
+
"condition": "OR",
|
19
|
+
"rules": [
|
20
|
+
{
|
21
|
+
"field": "lineItem/LineItemType",
|
22
|
+
"type": "string",
|
23
|
+
"operator": "equal",
|
24
|
+
"value": "Usage"
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"field": "lineItem/LineItemType",
|
28
|
+
"type": "string",
|
29
|
+
"operator": "equal",
|
30
|
+
"value": "DiscountedUsage"
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"field": "lineItem/LineItemType",
|
34
|
+
"type": "string",
|
35
|
+
"operator": "equal",
|
36
|
+
"value": "SavingsPlanCoveredUsage"
|
37
|
+
}
|
38
|
+
]
|
39
|
+
}
|
40
|
+
]
|
41
|
+
},
|
42
|
+
"exporter" : {
|
43
|
+
"types" : [{
|
44
|
+
"name" : "table",
|
45
|
+
"settings" : {
|
46
|
+
"title" : "Data Transfer by Product and Type"
|
47
|
+
}
|
48
|
+
}],
|
49
|
+
"properties" : [
|
50
|
+
"lineItem/UsageStartDate",
|
51
|
+
"lineItem/UsageAccountId",
|
52
|
+
"lineItem/ProductCode",
|
53
|
+
"lineItem/ResourceId",
|
54
|
+
"lineItem/UsageType",
|
55
|
+
"product/fromLocation",
|
56
|
+
"product/toLocation",
|
57
|
+
"product/productFamily",
|
58
|
+
"lineItem/UnblendedCost"
|
59
|
+
],
|
60
|
+
"actions" : {
|
61
|
+
"group_by" : [
|
62
|
+
"lineItem/ProductCode",
|
63
|
+
"lineItem/UsageAccountId",
|
64
|
+
"lineItem/UsageType",
|
65
|
+
"product/fromLocation",
|
66
|
+
"product/toLocation",
|
67
|
+
"product/productFamily"
|
68
|
+
],
|
69
|
+
"sort_by" : "lineItem/ProductCode",
|
70
|
+
"projection" : {
|
71
|
+
"type" : "sum",
|
72
|
+
"property" : "lineItem/UnblendedCost"
|
73
|
+
}
|
74
|
+
}
|
75
|
+
},
|
76
|
+
"column_overrides" : {
|
77
|
+
"lineItem/UsageStartDate" : {
|
78
|
+
"data_type": "date"
|
79
|
+
}
|
80
|
+
},
|
81
|
+
"__comments__" : {
|
82
|
+
"optional": {
|
83
|
+
"actions" : {
|
84
|
+
"group_by" : [
|
85
|
+
"lineItem/UsageStartDate",
|
86
|
+
"lineItem/ResourceId"
|
87
|
+
]
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
|