3scale_toolbox 0.16.0 → 0.18.3

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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/3scale_toolbox.gemspec +2 -2
  3. data/README.md +11 -8
  4. data/lib/3scale_toolbox.rb +3 -0
  5. data/lib/3scale_toolbox/3scale_client_factory.rb +3 -4
  6. data/lib/3scale_toolbox/cli/error_handler.rb +17 -14
  7. data/lib/3scale_toolbox/commands.rb +2 -0
  8. data/lib/3scale_toolbox/commands/backend_command/copy_command/copy_mapping_rules_task.rb +11 -27
  9. data/lib/3scale_toolbox/commands/backend_command/copy_command/copy_methods_task.rb +5 -10
  10. data/lib/3scale_toolbox/commands/backend_command/copy_command/copy_metrics_task.rb +4 -4
  11. data/lib/3scale_toolbox/commands/backend_command/copy_command/create_or_update_target_backend_task.rb +3 -2
  12. data/lib/3scale_toolbox/commands/backend_command/copy_command/task.rb +10 -32
  13. data/lib/3scale_toolbox/commands/import_command/issuer_type_transformer.rb +16 -0
  14. data/lib/3scale_toolbox/commands/import_command/openapi.rb +3 -0
  15. data/lib/3scale_toolbox/commands/import_command/openapi/create_activedocs_step.rb +3 -2
  16. data/lib/3scale_toolbox/commands/import_command/openapi/create_mapping_rule_step.rb +2 -1
  17. data/lib/3scale_toolbox/commands/import_command/openapi/create_method_step.rb +5 -14
  18. data/lib/3scale_toolbox/commands/import_command/openapi/step.rb +4 -0
  19. data/lib/3scale_toolbox/commands/import_command/openapi/update_service_proxy_step.rb +1 -0
  20. data/lib/3scale_toolbox/commands/methods_command/apply_command.rb +2 -4
  21. data/lib/3scale_toolbox/commands/methods_command/create_command.rb +0 -2
  22. data/lib/3scale_toolbox/commands/methods_command/delete_command.rb +1 -1
  23. data/lib/3scale_toolbox/commands/methods_command/list_command.rb +1 -9
  24. data/lib/3scale_toolbox/commands/metrics_command/list_command.rb +1 -1
  25. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_limits_step.rb +1 -1
  26. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_methods_step.rb +2 -2
  27. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_metrics_step.rb +2 -2
  28. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_pricing_rules_step.rb +1 -2
  29. data/lib/3scale_toolbox/commands/plans_command/export/step.rb +8 -20
  30. data/lib/3scale_toolbox/commands/plans_command/import/import_plan_limits_step.rb +12 -14
  31. data/lib/3scale_toolbox/commands/plans_command/import/import_plan_metrics_step.rb +6 -13
  32. data/lib/3scale_toolbox/commands/plans_command/import/import_plan_pricing_rules_step.rb +12 -20
  33. data/lib/3scale_toolbox/commands/plans_command/import/step.rb +2 -22
  34. data/lib/3scale_toolbox/commands/plans_command/list_command.rb +1 -1
  35. data/lib/3scale_toolbox/commands/plans_command/show_command.rb +1 -1
  36. data/lib/3scale_toolbox/commands/policies_command.rb +24 -0
  37. data/lib/3scale_toolbox/commands/policies_command/export_command.rb +98 -0
  38. data/lib/3scale_toolbox/commands/policies_command/import_command.rb +61 -0
  39. data/lib/3scale_toolbox/commands/product_command.rb +4 -0
  40. data/lib/3scale_toolbox/commands/product_command/copy_command.rb +7 -3
  41. data/lib/3scale_toolbox/commands/product_command/copy_command/copy_backends_task.rb +22 -5
  42. data/lib/3scale_toolbox/commands/product_command/export_command.rb +81 -0
  43. data/lib/3scale_toolbox/commands/product_command/import_command.rb +125 -0
  44. data/lib/3scale_toolbox/commands/proxy_config_command.rb +5 -0
  45. data/lib/3scale_toolbox/commands/proxy_config_command/deploy_command.rb +54 -0
  46. data/lib/3scale_toolbox/commands/proxy_config_command/export_command.rb +74 -0
  47. data/lib/3scale_toolbox/commands/proxy_config_command/helper.rb +15 -0
  48. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_activedocs_task.rb +15 -12
  49. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_app_plans_task.rb +15 -15
  50. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_limits_task.rb +12 -13
  51. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_mapping_rules_task.rb +11 -11
  52. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_methods_task.rb +9 -12
  53. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_metrics_task.rb +8 -8
  54. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_policies_task.rb +1 -1
  55. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_pricingrules_task.rb +15 -18
  56. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_service_proxy_task.rb +17 -2
  57. data/lib/3scale_toolbox/commands/service_command/copy_command/create_or_update_service_task.rb +2 -1
  58. data/lib/3scale_toolbox/commands/service_command/copy_command/destroy_mapping_rules_task.rb +9 -5
  59. data/lib/3scale_toolbox/commands/service_command/copy_command/task.rb +20 -34
  60. data/lib/3scale_toolbox/commands/update_command.rb +1 -1
  61. data/lib/3scale_toolbox/commands/update_command/service_command.rb +3 -2
  62. data/lib/3scale_toolbox/commands/update_command/service_command/delete_activedocs_task.rb +1 -3
  63. data/lib/3scale_toolbox/crds.rb +16 -0
  64. data/lib/3scale_toolbox/crds/application_plan_dump.rb +19 -0
  65. data/lib/3scale_toolbox/crds/backend_dump.rb +39 -0
  66. data/lib/3scale_toolbox/crds/backend_mapping_rule_dump.rb +26 -0
  67. data/lib/3scale_toolbox/crds/backend_method_dump.rb +12 -0
  68. data/lib/3scale_toolbox/crds/backend_metric_dump.rb +13 -0
  69. data/lib/3scale_toolbox/crds/backend_parser.rb +55 -0
  70. data/lib/3scale_toolbox/crds/backend_usage_dump.rb +11 -0
  71. data/lib/3scale_toolbox/crds/limit_dump.rb +37 -0
  72. data/lib/3scale_toolbox/crds/mapping_rule_dump.rb +26 -0
  73. data/lib/3scale_toolbox/crds/method_dump.rb +12 -0
  74. data/lib/3scale_toolbox/crds/metric_dump.rb +13 -0
  75. data/lib/3scale_toolbox/crds/pricing_rule_dump.rb +38 -0
  76. data/lib/3scale_toolbox/crds/product_deployment_parser.rb +329 -0
  77. data/lib/3scale_toolbox/crds/product_dump.rb +157 -0
  78. data/lib/3scale_toolbox/crds/product_parser.rb +114 -0
  79. data/lib/3scale_toolbox/crds/remote.rb +682 -0
  80. data/lib/3scale_toolbox/entities.rb +3 -0
  81. data/lib/3scale_toolbox/entities/activedocs.rb +12 -0
  82. data/lib/3scale_toolbox/entities/application_plan.rb +74 -39
  83. data/lib/3scale_toolbox/entities/backend.rb +65 -30
  84. data/lib/3scale_toolbox/entities/backend_mapping_rule.rb +29 -3
  85. data/lib/3scale_toolbox/entities/backend_method.rb +25 -16
  86. data/lib/3scale_toolbox/entities/backend_metric.rb +12 -2
  87. data/lib/3scale_toolbox/entities/backend_usage.rb +7 -1
  88. data/lib/3scale_toolbox/entities/limit.rb +71 -0
  89. data/lib/3scale_toolbox/entities/mapping_rule.rb +90 -0
  90. data/lib/3scale_toolbox/entities/method.rb +33 -19
  91. data/lib/3scale_toolbox/entities/metric.rb +29 -18
  92. data/lib/3scale_toolbox/entities/pricing_rule.rb +63 -0
  93. data/lib/3scale_toolbox/entities/proxy_config.rb +0 -1
  94. data/lib/3scale_toolbox/entities/service.rb +149 -46
  95. data/lib/3scale_toolbox/error.rb +50 -0
  96. data/lib/3scale_toolbox/helper.rb +13 -16
  97. data/lib/3scale_toolbox/openapi/oas3.rb +1 -1
  98. data/lib/3scale_toolbox/proxy_logger.rb +4 -0
  99. data/lib/3scale_toolbox/remote_cache.rb +157 -0
  100. data/lib/3scale_toolbox/remotes.rb +2 -2
  101. data/lib/3scale_toolbox/version.rb +1 -1
  102. data/licenses.xml +113 -45
  103. metadata +37 -8
@@ -0,0 +1,114 @@
1
+ module ThreeScaleToolbox
2
+ module CRD
3
+ class ProductParser
4
+ Metric = Struct.new(:system_name, :friendly_name, :description, :unit)
5
+ Method = Struct.new(:system_name, :friendly_name, :description)
6
+ MappingRule = Struct.new(:metric_ref, :http_method, :pattern, :delta, :last)
7
+ BackendUsage = Struct.new(:backend_system_name, :path)
8
+ ApplicationPlan = Struct.new(:system_name, :name, :approval_required, :trial_period_days, :setup_fee, :custom, :state, :cost_per_month, :limits, :pricing_rules)
9
+ Limit = Struct.new(:period, :value, :metric_system_name, :backend_system_name) do
10
+ def to_s
11
+ {period: period, value: value}.to_json
12
+ end
13
+ end
14
+ PricingRule = Struct.new(:from, :to, :price_per_unit, :metric_system_name, :backend_system_name) do
15
+ def to_s
16
+ {from: from, to: to, price_per_unit: price_per_unit}.to_json
17
+ end
18
+ end
19
+ PolicyChainItem = Struct.new(:name, :version, :configuration, :enabled)
20
+
21
+ attr_reader :cr, :deployment_parser
22
+
23
+ def initialize(cr)
24
+ @cr = cr
25
+ @deployment_parser = ProductDeploymentParser.new(cr.dig('spec', 'deployment') || {})
26
+ end
27
+
28
+ def system_name
29
+ cr.dig('spec', 'systemName')
30
+ end
31
+
32
+ def name
33
+ cr.dig('spec', 'name')
34
+ end
35
+
36
+ def description
37
+ cr.dig('spec', 'description')
38
+ end
39
+
40
+ def metrics
41
+ @metrics ||= (cr.dig('spec', 'metrics') || {}).map do |system_name, metric|
42
+ Metric.new(system_name, metric['friendlyName'], metric['description'], metric['unit'])
43
+ end
44
+ end
45
+
46
+ def methods
47
+ @methods ||= (cr.dig('spec', 'methods') || {}).map do |system_name, method|
48
+ Method.new(system_name, method['friendlyName'], method['description'])
49
+ end
50
+ end
51
+
52
+ def mapping_rules
53
+ @mapping_rules ||= (cr.dig('spec', 'mappingRules') || []).map do |mapping_rule|
54
+ MappingRule.new(mapping_rule['metricMethodRef'], mapping_rule['httpMethod'],
55
+ mapping_rule['pattern'], mapping_rule['increment'], mapping_rule['last'])
56
+ end
57
+ end
58
+
59
+ # Metrics and methods index by system_name
60
+ def metrics_index
61
+ @metrics_index ||= (methods + metrics).each_with_object({}) { |metric, h| h[metric.system_name] = metric }
62
+ end
63
+
64
+ def application_plans
65
+ @application_plans ||= (cr.dig('spec', 'applicationPlans') || {}).map do |system_name, plan|
66
+ ApplicationPlan.new(system_name, plan['name'], plan['appsRequireApproval'],
67
+ plan['trialPeriod'], plan['setupFee'], plan['custom'], plan['state'],
68
+ plan['costMonth'], parse_limits(plan), parse_pricing_rules(plan))
69
+ end
70
+ end
71
+
72
+ def backend_usages
73
+ @backend_usages ||= (cr.dig('spec', 'backendUsages') || {}).map do |backend_system_name, usage|
74
+ BackendUsage.new(backend_system_name, usage['path'])
75
+ end
76
+ end
77
+
78
+ %i[deployment_option backend_version credentials_location auth_app_key
79
+ auth_app_id auth_user_key error_auth_failed error_auth_missing error_status_auth_failed
80
+ error_headers_auth_failed error_status_auth_missing error_headers_auth_missing error_no_match
81
+ error_status_no_match error_headers_no_match error_limits_exceeded
82
+ error_status_limits_exceeded error_headers_limits_exceeded secret_token hostname_rewrite
83
+ endpoint sandbox_endpoint oidc_issuer_endpoint oidc_issuer_type jwt_claim_with_client_id
84
+ jwt_claim_with_client_id_type standard_flow_enabled implicit_flow_enabled
85
+ service_accounts_enabled direct_access_grants_enabled].each do |method_name|
86
+ define_method method_name do
87
+ deployment_parser.public_send(method_name)
88
+ end
89
+ end
90
+
91
+ def policy_chain
92
+ @policy_chain ||= (cr.dig('spec', 'policies') || []).map do |item|
93
+ PolicyChainItem.new(item['name'], item['version'], item['configuration'], item['enabled'])
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def parse_limits(plan)
100
+ plan.fetch('limits', []).map do |limit|
101
+ Limit.new(limit['period'], limit['value'],
102
+ limit.dig('metricMethodRef', 'systemName'), limit.dig('metricMethodRef', 'backend'))
103
+ end
104
+ end
105
+
106
+ def parse_pricing_rules(plan)
107
+ plan.fetch('pricingRules', []).map do |pr|
108
+ PricingRule.new(pr['from'], pr['to'], pr['pricePerUnit'],
109
+ pr.dig('metricMethodRef', 'systemName'), pr.dig('metricMethodRef', 'backend'))
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,682 @@
1
+ module ThreeScaleToolbox
2
+ module CRD
3
+ class Remote
4
+
5
+ #
6
+ # Product CRD Format
7
+ # https://github.com/3scale/3scale-operator/blob/3scale-2.10.0-CR2/doc/product-reference.md
8
+ #
9
+ # apiVersion: capabilities.3scale.net/v1beta1
10
+ # kind: Product
11
+ # metadata:
12
+ # annotations:
13
+ # 3scale_toolbox_created_at: '2021-02-10T09:16:59Z'
14
+ # 3scale_toolbox_version: 0.17.1
15
+ # name: api.vygczmih
16
+ # spec:
17
+ # name: Default API
18
+ # systemName: api
19
+ # description: ''
20
+ # mappingRules:
21
+ # - httpMethod: GET
22
+ # pattern: "/v1"
23
+ # metricMethodRef: servicemethod01
24
+ # increment: 1
25
+ # last: false
26
+ # metrics:
27
+ # hits:
28
+ # friendlyName: Hits
29
+ # unit: hit
30
+ # description: Number of API hits
31
+ # methods:
32
+ # servicemethod01:
33
+ # friendlyName: servicemethod01
34
+ # description: ''
35
+ # policies:
36
+ # - name: apicast
37
+ # version: builtin
38
+ # configuration: {}
39
+ # enabled: true
40
+ # backendUsages:
41
+ # backend_01:
42
+ # path: "/v1/pets"
43
+ # backend_02:
44
+ # path: "/v1/cats"
45
+ # deployment:
46
+ # apicastSelfManaged:
47
+ # authentication:
48
+ # appKeyAppID:
49
+ # appID: app_id
50
+ # appKey: app_key
51
+ # credentials: query
52
+ # security:
53
+ # hostHeader: ''
54
+ # secretToken: some_secret
55
+ # gatewayResponse:
56
+ # errorStatusAuthFailed: 403
57
+ # errorHeadersAuthFailed: text/plain; charset=us-ascii
58
+ # errorAuthFailed: Authentication failed
59
+ # errorStatusAuthMissing: 403
60
+ # errorHeadersAuthMissing: text/plain; charset=us-ascii
61
+ # errorAuthMissing: Authentication parameters missing
62
+ # errorStatusNoMatch: 404
63
+ # errorHeadersNoMatch: text/plain; charset=us-ascii
64
+ # errorNoMatch: No Mapping Rule matched
65
+ # errorStatusLimitsExceeded: 429
66
+ # errorHeadersLimitsExceeded: text/plain; charset=us-ascii
67
+ # errorLimitsExceeded: Usage limit exceeded
68
+ # stagingPublicBaseURL: http://staging.example.com:80
69
+ # productionPublicBaseURL: http://example.com:80
70
+ # applicationPlans:
71
+ # basic:
72
+ # name: Basic
73
+ # appsRequireApproval: false
74
+ # trialPeriod: 0
75
+ # setupFee: 0.0
76
+ # custom: false
77
+ # state: published
78
+ # costMonth: 0.0
79
+ # pricingRules:
80
+ # - from: 1
81
+ # to: 1000
82
+ # pricePerUnit: 1.0
83
+ # metricMethodRef:
84
+ # systemName: hits
85
+ # limits:
86
+ # - period: eternity
87
+ # value: 10000
88
+ # metricMethodRef:
89
+ # systemName: hits
90
+ #
91
+ # Backend CRD format
92
+ # https://github.com/3scale/3scale-operator/blob/3scale-2.10.0-CR2/doc/backend-reference.md
93
+ #
94
+ # apiVersion: capabilities.3scale.net/v1beta1
95
+ # kind: Backend
96
+ # metadata:
97
+ # annotations:
98
+ # 3scale_toolbox_created_at: '2021-02-10T09:17:12Z'
99
+ # 3scale_toolbox_version: 0.17.1
100
+ # name: backend.01.rxoeasvk
101
+ # spec:
102
+ # name: Backend 01
103
+ # systemName: backend_01
104
+ # privateBaseURL: https://echo-api.3scale.net:443
105
+ # description: new desc
106
+ # mappingRules:
107
+ # - httpMethod: GET
108
+ # pattern: "/v1/pets"
109
+ # metricMethodRef: mybackendmethod01
110
+ # increment: 1
111
+ # last: false
112
+ # metrics:
113
+ # hits:
114
+ # friendlyName: Hits
115
+ # unit: hit
116
+ # description: Number of API hits
117
+ # methods:
118
+ # mybackendmethod01:
119
+ # friendlyName: mybackendmethod01
120
+ # description: ''
121
+
122
+ attr_reader :product_index, :backend_index
123
+
124
+ def initialize(products, backends)
125
+ # Index of backends by id (by sequence order)
126
+ @backend_index = backends.each_with_object({}) { |backend, hash| hash[new_index] = backend }
127
+
128
+ # Index of products by id (by sequence order)
129
+ @product_index = products.each_with_object({}) { |product, hash| hash[new_index] = product }
130
+
131
+ validate!
132
+ end
133
+
134
+ def http_client
135
+ Struct.new(:endpoint).new('http://fromCR')
136
+ end
137
+
138
+ def show_service(service_id)
139
+ service = product_index.fetch(service_id) { raise_product_missing(service_id) }
140
+ {
141
+ 'id' => service_id,
142
+ 'name' => service.name,
143
+ 'system_name' => service.system_name,
144
+ 'description' => service.description,
145
+ 'deployment_option' => service.deployment_option,
146
+ 'backend_version' => service.backend_version
147
+ }
148
+ end
149
+
150
+ def list_services(page:, per_page:)
151
+ product_index.keys.map(&method(:show_service))
152
+ end
153
+
154
+ def backend(backend_id)
155
+ b = backend_index.fetch(backend_id) { raise_backend_missing(backend_id) }
156
+ {
157
+ 'id' => backend_id,
158
+ 'name' => b.name,
159
+ 'system_name' => b.system_name,
160
+ 'description' => b.description,
161
+ 'private_endpoint' => b.private_endpoint
162
+ }
163
+ end
164
+
165
+ def list_backend_usages(service_id)
166
+ service = product_index.fetch(service_id) { raise_product_missing(service_id) }
167
+ service.backend_usages.each_with_index.map do |backend_usage, idx|
168
+ {
169
+ 'id' => idx + 1,
170
+ 'path' => backend_usage.path,
171
+ 'service_id' => service_id,
172
+ 'backend_id' => backend_index.find { |k, b| b.system_name == backend_usage.backend_system_name }.first
173
+ }
174
+ end
175
+ end
176
+
177
+ # return metrics and methods
178
+ def list_backend_metrics(backend_id)
179
+ metric_index = backend_metric_index.fetch(backend_id) { raise_backend_missing(backend_id) }
180
+
181
+ # only metrics, not methods
182
+ backend_metric_system_name_list = backend_index.fetch(backend_id).metrics.map(&:system_name)
183
+
184
+ # select only metrics
185
+ backend_metric_only_index = metric_index.select do |_, metric|
186
+ backend_metric_system_name_list.include? metric.system_name
187
+ end
188
+
189
+ backend_metric_only_index.map do |metric_id, metric|
190
+ {
191
+ 'id' => metric_id,
192
+ 'friendly_name' => metric.friendly_name,
193
+ 'system_name' => metric.system_name,
194
+ 'description' => metric.description,
195
+ 'unit' => metric.unit
196
+ }
197
+ end + list_backend_methods(backend_id, 0)
198
+ end
199
+
200
+ def list_backend_methods(backend_id, _)
201
+ metric_index = backend_metric_index.fetch(backend_id) { raise_backend_missing(backend_id) }
202
+
203
+ backend_method_system_name_list = backend_index.fetch(backend_id).methods.map(&:system_name)
204
+
205
+ # select only methods
206
+ backend_method_index = metric_index.select do |_, metric|
207
+ backend_method_system_name_list.include? metric.system_name
208
+ end
209
+
210
+ backend_method_index.map do |method_id, method|
211
+ {
212
+ 'id' => method_id,
213
+ 'parent_id' => 1, # should not be used
214
+ 'friendly_name' => method.friendly_name,
215
+ 'system_name' => method.system_name,
216
+ 'description' => method.description
217
+ }
218
+ end
219
+ end
220
+
221
+ def list_backend_mapping_rules(backend_id)
222
+ metric_index = backend_metric_index.fetch(backend_id) { raise_backend_missing(backend_id) }
223
+
224
+ backend_index.fetch(backend_id).mapping_rules.each_with_index.map do |mapping_rule, mapping_id|
225
+ {
226
+ # 0 is not valid id
227
+ 'id' => mapping_id + 1,
228
+ 'pattern' => mapping_rule.pattern,
229
+ 'http_method' => mapping_rule.http_method,
230
+ 'delta' => mapping_rule.delta,
231
+ 'last' => mapping_rule.last,
232
+ # Previous validation assures mapping rule metric references are valid
233
+ 'metric_id' => metric_index.find { |_, metric| metric.system_name == mapping_rule.metric_ref }.first
234
+ }
235
+ end
236
+ end
237
+
238
+ def show_proxy(service_id)
239
+ service = product_index.fetch(service_id) { raise_product_missing(service_id) }
240
+ {
241
+ 'endpoint' => service.endpoint,
242
+ 'credentials_location' => service.credentials_location,
243
+ 'auth_app_key' => service.auth_app_key,
244
+ 'auth_app_id' => service.auth_app_id,
245
+ 'auth_user_key' => service.auth_user_key,
246
+ 'error_auth_failed' => service.error_auth_failed,
247
+ 'error_auth_missing' => service.error_auth_missing,
248
+ 'error_status_auth_failed' => service.error_status_auth_failed,
249
+ 'error_headers_auth_failed' => service.error_headers_auth_failed,
250
+ 'error_status_auth_missing' => service.error_status_auth_missing,
251
+ 'error_headers_auth_missing' => service.error_headers_auth_missing,
252
+ 'error_no_match' => service.error_no_match,
253
+ 'error_status_no_match' => service.error_status_no_match,
254
+ 'error_headers_no_match' => service.error_headers_no_match,
255
+ 'error_limits_exceeded' => service.error_limits_exceeded,
256
+ 'error_status_limits_exceeded' => service.error_status_limits_exceeded,
257
+ 'error_headers_limits_exceeded' => service.error_headers_limits_exceeded,
258
+ 'secret_token' => service.secret_token,
259
+ 'hostname_rewrite' => service.hostname_rewrite,
260
+ 'sandbox_endpoint' => service.sandbox_endpoint,
261
+ 'oidc_issuer_endpoint' => service.oidc_issuer_endpoint,
262
+ 'oidc_issuer_type' => service.oidc_issuer_type,
263
+ 'jwt_claim_with_client_id' => service.jwt_claim_with_client_id,
264
+ 'jwt_claim_with_client_id_type' => service.jwt_claim_with_client_id_type
265
+ }.delete_if { |k,v| v.nil? }
266
+ end
267
+
268
+ def show_oidc(service_id)
269
+ service = product_index.fetch(service_id) { raise_product_missing(service_id) }
270
+ {
271
+ 'id' => service_id, #should not be used
272
+ 'standard_flow_enabled' => service.standard_flow_enabled,
273
+ 'implicit_flow_enabled' => service.implicit_flow_enabled,
274
+ 'service_accounts_enabled' => service.service_accounts_enabled,
275
+ 'direct_access_grants_enabled' => service.direct_access_grants_enabled
276
+ }
277
+ end
278
+
279
+ def list_metrics(service_id)
280
+ metric_index = product_metric_index.fetch(service_id) { raise_product_missing(service_id) }
281
+
282
+ # only metrics, not methods
283
+ product_metric_system_name_list = product_index.fetch(service_id).metrics.map(&:system_name)
284
+
285
+ # select only metrics
286
+ product_metric_only_index = metric_index.select do |_, metric|
287
+ product_metric_system_name_list.include? metric.system_name
288
+ end
289
+
290
+ product_metric_only_index.map do |metric_id, metric|
291
+ {
292
+ 'id' => metric_id,
293
+ 'friendly_name' => metric.friendly_name,
294
+ 'system_name' => metric.system_name,
295
+ 'description' => metric.description,
296
+ 'unit' => metric.unit
297
+ }
298
+ end + list_methods(service_id, 0)
299
+ end
300
+
301
+ def list_methods(service_id, _)
302
+ metric_index = product_metric_index.fetch(service_id) { raise_product_missing(service_id) }
303
+
304
+ product_method_system_name_list = product_index.fetch(service_id).methods.map(&:system_name)
305
+
306
+ # select only methods
307
+ product_method_index = metric_index.select do |_, metric|
308
+ product_method_system_name_list.include? metric.system_name
309
+ end
310
+
311
+ product_method_index.map do |method_id, method|
312
+ {
313
+ 'id' => method_id,
314
+ 'parent_id' => 1, # should not be used
315
+ 'friendly_name' => method.friendly_name,
316
+ 'system_name' => method.system_name,
317
+ 'description' => method.description
318
+ }
319
+ end
320
+ end
321
+
322
+ def list_mapping_rules(service_id)
323
+ metric_index = product_metric_index.fetch(service_id) { raise_product_missing(service_id) }
324
+
325
+ product_index.fetch(service_id).mapping_rules.each_with_index.map do |mapping_rule, mapping_id|
326
+ {
327
+ # 0 is not valid id
328
+ 'id' => mapping_id + 1,
329
+ 'pattern' => mapping_rule.pattern,
330
+ 'http_method' => mapping_rule.http_method,
331
+ 'delta' => mapping_rule.delta,
332
+ 'last' => mapping_rule.last,
333
+ # Previous validation assures mapping rule metric references are valid
334
+ 'metric_id' => metric_index.find { |_, metric| metric.system_name == mapping_rule.metric_ref }.first
335
+ }
336
+ end
337
+ end
338
+
339
+ def list_service_application_plans(service_id)
340
+ plan_index = product_plan_index.fetch(service_id) { raise_product_missing(service_id) }
341
+
342
+ plan_index.map do |plan_id, plan|
343
+ {
344
+ 'id' => plan_id,
345
+ 'name' => plan.name,
346
+ 'setup_fee' => plan.setup_fee,
347
+ 'custom' => plan.custom,
348
+ 'state' => plan.state,
349
+ 'cost_per_month' => plan.cost_per_month,
350
+ 'trial_period_days' => plan.trial_period_days,
351
+ 'approval_required' => plan.approval_required,
352
+ 'system_name' => plan.system_name
353
+ }
354
+ end
355
+ end
356
+
357
+ def list_application_plan_limits(plan_id)
358
+ plan = application_plan_index.fetch(plan_id) { raise_plan_missing(plan_id) }
359
+ plan.limits.map do |limit|
360
+ {
361
+ 'id' => 1, # should not be used
362
+ 'period' => limit.period,
363
+ 'value' => limit.value,
364
+ 'metric_id' => find_metric_id_from_ref(plan_id, limit.metric_system_name, limit.backend_system_name),
365
+ 'plan_id' => plan_id
366
+ }
367
+ end
368
+ end
369
+
370
+ def show_policies(service_id)
371
+ service = product_index.fetch(service_id) { raise_product_missing(service_id) }
372
+ service.policy_chain.map do |policy_chain_item|
373
+ {
374
+ 'name' => policy_chain_item.name,
375
+ 'version' => policy_chain_item.version,
376
+ 'configuration' => policy_chain_item.configuration,
377
+ 'enabled' => policy_chain_item.enabled
378
+ }
379
+ end
380
+ end
381
+
382
+ def list_pricingrules_per_application_plan(plan_id)
383
+ plan = application_plan_index.fetch(plan_id) { raise_plan_missing(plan_id) }
384
+ plan.pricing_rules.map do |pr|
385
+ {
386
+ 'id' => 1, # should not be used
387
+ 'cost_per_unit' => pr.price_per_unit,
388
+ 'min' => pr.from,
389
+ 'max' => pr.to,
390
+ 'metric_id' => find_metric_id_from_ref(plan_id, pr.metric_system_name, pr.backend_system_name),
391
+ 'plan_id' => plan_id
392
+ }
393
+ end
394
+ end
395
+
396
+ def list_activedocs
397
+ []
398
+ end
399
+
400
+ def delete_service(id)
401
+ true
402
+ end
403
+
404
+ def delete_backend(id)
405
+ true
406
+ end
407
+
408
+ def delete_backend_usage(product_id, id)
409
+ true
410
+ end
411
+
412
+ private
413
+
414
+ def validate!
415
+ validate_product_metric_method_uniqueness!
416
+
417
+ validate_product_mapping_rule_references!
418
+
419
+ validate_product_backend_usage_references!
420
+
421
+ validate_product_application_plan_limit_backend_references!
422
+
423
+ validate_product_application_plan_limit_backend_metric_references!
424
+
425
+ validate_product_application_plan_limit_metric_references!
426
+
427
+ validate_product_application_plan_pricingrule_backend_references!
428
+
429
+ validate_product_application_plan_pricingrule_backend_metric_references!
430
+
431
+ validate_product_application_plan_pricingrule_metric_references!
432
+
433
+ validate_backend_metric_method_uniqueness!
434
+
435
+ validate_backend_mapping_rule_references!
436
+ end
437
+
438
+ def validate_product_metric_method_uniqueness!
439
+ product_index.each_value do |product|
440
+ system_name_list = product.metrics.map(&:system_name) + product.methods.map(&:system_name)
441
+ if system_name_list.length != system_name_list.uniq.length
442
+ raise ThreeScaleToolbox::Error, "Invalid content. " \
443
+ "Product #{product.system_name} contains metrics and method system names that are not unique"
444
+ end
445
+ end
446
+ end
447
+
448
+ def validate_product_mapping_rule_references!
449
+ product_index.each_value do |product|
450
+ product.mapping_rules.each do |mapping_rule|
451
+ product.metrics_index.fetch(mapping_rule.metric_ref) do
452
+ raise ThreeScaleToolbox::Error, "Invalid content. " \
453
+ "Product {#{product.system_name} " \
454
+ "Mapping rule {#{mapping_rule.http_method} #{mapping_rule.pattern}} " \
455
+ "referencing to metric #{mapping_rule.metric_ref} has not been found"
456
+ end
457
+ end
458
+ end
459
+ end
460
+
461
+ def validate_product_backend_usage_references!
462
+ product_index.each_value do |product|
463
+ product.backend_usages.each do |backend_usage|
464
+ if backend_index.values.none? { |backend| backend.system_name == backend_usage.backend_system_name }
465
+ raise ThreeScaleToolbox::Error, "Invalid content. Product {#{product.system_name}" \
466
+ "backend usage reference to backend #{backend_usage.backend_system_name} has not been found"
467
+ end
468
+ end
469
+ end
470
+ end
471
+
472
+ # validate limit backend references
473
+ def validate_product_application_plan_limit_backend_references!
474
+ product_index.each_value do |product|
475
+ product.application_plans.each do |plan|
476
+ limits_with_backend_ref = plan.limits.reject { |limit| limit.backend_system_name.nil? }
477
+ limits_with_backend_ref.each do |limit|
478
+ unless product.backend_usages.map(&:backend_system_name).include? limit.backend_system_name
479
+ raise ThreeScaleToolbox::Error, "Invalid content. " \
480
+ "Product {#{product.system_name} " \
481
+ "Limit {#{limit.to_s}}, the backend #{limit.backend_system_name} " \
482
+ "has not been found in backend usages"
483
+ end
484
+ end
485
+ end
486
+ end
487
+ end
488
+
489
+ # validate limit metric with backend references
490
+ def validate_product_application_plan_limit_backend_metric_references!
491
+ product_index.each_value do |product|
492
+ product.application_plans.each do |plan|
493
+ limits_with_backend_ref = plan.limits.reject { |limit| limit.backend_system_name.nil? }
494
+ limits_with_backend_ref.each do |limit|
495
+ # It is already validated that backend references are correct, hence it must exist
496
+ limit_backend_ref = backend_index.values.find { |b| b.system_name == limit.backend_system_name }
497
+ unless (limit_backend_ref.methods + limit_backend_ref.metrics).map(&:system_name).include? limit.metric_system_name
498
+ raise ThreeScaleToolbox::Error, "Invalid content. " \
499
+ "Product {#{product.system_name} " \
500
+ "Limit {#{limit.to_s}}, the metric #{limit.metric_system_name} " \
501
+ "has not been found in backend #{limit_backend_ref.system_name}"
502
+ end
503
+ end
504
+ end
505
+ end
506
+ end
507
+
508
+ # validate limit metric references in product metrics and methods
509
+ def validate_product_application_plan_limit_metric_references!
510
+ product_index.each_value do |product|
511
+ product.application_plans.each do |plan|
512
+ limits_with_product_ref = plan.limits.select { |limit| limit.backend_system_name.nil? }
513
+ limits_with_product_ref.each do |limit|
514
+ unless (product.methods + product.metrics).map(&:system_name).include? limit.metric_system_name
515
+ raise ThreeScaleToolbox::Error, "Invalid content. " \
516
+ "Product {#{product.system_name} " \
517
+ "Limit {#{limit.to_s}}, the metric #{limit.metric_system_name} " \
518
+ "has not been found"
519
+ end
520
+ end
521
+ end
522
+ end
523
+ end
524
+
525
+ # validate pricing rules backend references
526
+ def validate_product_application_plan_pricingrule_backend_references!
527
+ product_index.each_value do |product|
528
+ product.application_plans.each do |plan|
529
+ pr_list_with_backend_ref = plan.pricing_rules.reject { |pr| pr.backend_system_name.nil? }
530
+ pr_list_with_backend_ref.each do |pr|
531
+ unless product.backend_usages.map(&:backend_system_name).include? pr.backend_system_name
532
+ raise ThreeScaleToolbox::Error, "Invalid content. " \
533
+ "Product {#{product.system_name} " \
534
+ "PricingRule {#{pr.to_s}}, the backend #{pr.backend_system_name} " \
535
+ "has not been found in backend usages"
536
+ end
537
+ end
538
+ end
539
+ end
540
+ end
541
+
542
+ # validate pricing rule metric with backend references
543
+ def validate_product_application_plan_pricingrule_backend_metric_references!
544
+ product_index.each_value do |product|
545
+ product.application_plans.each do |plan|
546
+ pr_list_with_backend_ref = plan.pricing_rules.reject { |pr| pr.backend_system_name.nil? }
547
+ pr_list_with_backend_ref.each do |pr|
548
+ # It is already validated that backend references are correct, hence it must exist
549
+ pr_backend_ref = backend_index.values.find { |b| b.system_name == pr.backend_system_name }
550
+ unless (pr_backend_ref.methods + pr_backend_ref.metrics).map(&:system_name).include? pr.metric_system_name
551
+ raise ThreeScaleToolbox::Error, "Invalid content. " \
552
+ "Product {#{product.system_name} " \
553
+ "PricingRule {#{pr.to_s}}, the metric #{pr.metric_system_name} " \
554
+ "has not been found in backend #{pr_backend_ref.system_name}"
555
+ end
556
+ end
557
+ end
558
+ end
559
+ end
560
+
561
+ # validate pricing rule metric references in product metrics and methods
562
+ def validate_product_application_plan_pricingrule_metric_references!
563
+ product_index.each_value do |product|
564
+ product.application_plans.each do |plan|
565
+ pr_list_with_product_ref = plan.pricing_rules.select { |pr| pr.backend_system_name.nil? }
566
+ pr_list_with_product_ref.each do |pr|
567
+ unless (product.methods + product.metrics).map(&:system_name).include? pr.metric_system_name
568
+ raise ThreeScaleToolbox::Error, "Invalid content. " \
569
+ "Product {#{product.system_name} " \
570
+ "PricingRule {#{pr.to_s}}, the metric #{pr.metric_system_name} " \
571
+ "has not been found"
572
+ end
573
+ end
574
+ end
575
+ end
576
+ end
577
+
578
+ def validate_backend_metric_method_uniqueness!
579
+ backend_index.each_value do |backend|
580
+ system_name_list = backend.metrics.map(&:system_name) + backend.methods.map(&:system_name)
581
+ if system_name_list.length != system_name_list.uniq.length
582
+ raise ThreeScaleToolbox::Error, "Invalid content. " \
583
+ "Backend #{backend.system_name} contains metrics and method system names that are not unique"
584
+ end
585
+ end
586
+ end
587
+
588
+ def validate_backend_mapping_rule_references!
589
+ backend_index.each_value do |backend|
590
+ backend.mapping_rules.each do |mapping_rule|
591
+ backend.metrics_index.fetch(mapping_rule.metric_ref) do
592
+ raise ThreeScaleToolbox::Error, "Invalid content. " \
593
+ "Backend {#{backend.system_name} " \
594
+ "Mapping rule {#{mapping_rule.http_method} #{mapping_rule.pattern}} " \
595
+ "referencing to metric #{mapping_rule.metric_ref} has not been found"
596
+ end
597
+ end
598
+ end
599
+ end
600
+
601
+
602
+ # Index: backend_id -> metric_id -> metric or method
603
+ # metric and methods have unique indexes
604
+ def backend_metric_index
605
+ @backend_metric_index ||= backend_index.each_with_object({}) do |(backend_id, backend), backend_index|
606
+ metric_method_list = backend.metrics + backend.methods
607
+ backend_index[backend_id] = metric_method_list.each_with_object({}) do |metric, metric_index|
608
+ metric_index[new_index] = metric
609
+ end
610
+ end
611
+ end
612
+
613
+ # Index: plan_id -> plan
614
+ def application_plan_index
615
+ product_plan_index.values.inject({}) { |acc, plan_index| acc.merge(plan_index) }
616
+ end
617
+
618
+ # Index: product_id -> plan_id -> plan
619
+ def product_plan_index
620
+ @product_plan_index ||= product_index.each_with_object({}) do |(product_id, product), product_index|
621
+ product_index[product_id] = product.application_plans.each_with_object({}) do |plan, plan_index|
622
+ plan_index[new_index] = plan
623
+ end
624
+ end
625
+ end
626
+
627
+ # Index: plan_id -> product_id
628
+ def plan_product_index
629
+ @plan_product_index ||= product_plan_index.each_with_object({}) do |(product_id, plan_index), hash|
630
+ plan_index.keys.each { |plan_id| hash[plan_id] = product_id }
631
+ end
632
+ end
633
+
634
+ # Index: product_id -> metric_id -> metric
635
+ def product_metric_index
636
+ @product_metric_index ||= product_index.each_with_object({}) do |(product_id, product), product_index|
637
+ metric_method_list = product.metrics + product.methods
638
+ product_index[product_id] = metric_method_list.each_with_object({}) do |metric, metric_index|
639
+ metric_index[new_index] = metric
640
+ end
641
+ end
642
+ end
643
+
644
+ def new_index
645
+ # starts on 1
646
+ @new_index ||= 0
647
+ @new_index += 1
648
+ end
649
+
650
+ def find_metric_id_from_ref(plan_id, system_name, backend_system_name)
651
+ if backend_system_name.nil?
652
+ product_id = plan_product_index.fetch(plan_id)
653
+ product_metric_index.fetch(product_id).find do |_, metric|
654
+ metric.system_name == system_name
655
+ end.first
656
+ else
657
+ # it is validated that backend is in application backend usages
658
+ # so it can be safely search in the whole backend index, and must exist
659
+ backend_id = backend_index.find { |_, b| b.system_name == backend_system_name }.first
660
+ backend_metric_index.fetch(backend_id).find do |_, metric|
661
+ metric.system_name == system_name
662
+ end.first
663
+ end
664
+ end
665
+
666
+ def raise_backend_missing(backend_id)
667
+ raise ThreeScaleToolbox::Error, "Unexpected event in CRDRemote class. " \
668
+ "Backend #{backend_id} not found in the index"
669
+ end
670
+
671
+ def raise_product_missing(product_id)
672
+ raise ThreeScaleToolbox::Error, "Unexpected event in CRDRemote class. " \
673
+ "Service #{product_id} not found in the index"
674
+ end
675
+
676
+ def raise_plan_missing(plan_id)
677
+ raise ThreeScaleToolbox::Error, "Unexpected event in CRDRemote class. " \
678
+ "ApplicationPlan #{plan_id} not found in the index"
679
+ end
680
+ end
681
+ end
682
+ end