pal_tool 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|