3scale_toolbox 0.17.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/3scale_toolbox.gemspec +4 -4
  3. data/README.md +7 -5
  4. data/lib/3scale_toolbox.rb +3 -0
  5. data/lib/3scale_toolbox/3scale_client_factory.rb +3 -4
  6. data/lib/3scale_toolbox/commands.rb +2 -0
  7. data/lib/3scale_toolbox/commands/backend_command/copy_command.rb +3 -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/delete_mapping_rules_task.rb +18 -0
  13. data/lib/3scale_toolbox/commands/backend_command/copy_command/task.rb +14 -32
  14. data/lib/3scale_toolbox/commands/import_command/issuer_type_transformer.rb +16 -0
  15. data/lib/3scale_toolbox/commands/import_command/openapi.rb +5 -0
  16. data/lib/3scale_toolbox/commands/import_command/openapi/create_activedocs_step.rb +3 -2
  17. data/lib/3scale_toolbox/commands/import_command/openapi/create_mapping_rule_step.rb +2 -1
  18. data/lib/3scale_toolbox/commands/import_command/openapi/create_method_step.rb +5 -14
  19. data/lib/3scale_toolbox/commands/import_command/openapi/step.rb +4 -0
  20. data/lib/3scale_toolbox/commands/import_command/openapi/update_service_proxy_step.rb +1 -0
  21. data/lib/3scale_toolbox/commands/methods_command/apply_command.rb +2 -4
  22. data/lib/3scale_toolbox/commands/methods_command/create_command.rb +0 -2
  23. data/lib/3scale_toolbox/commands/methods_command/delete_command.rb +1 -1
  24. data/lib/3scale_toolbox/commands/methods_command/list_command.rb +1 -9
  25. data/lib/3scale_toolbox/commands/metrics_command/list_command.rb +1 -1
  26. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_limits_step.rb +1 -1
  27. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_methods_step.rb +2 -2
  28. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_metrics_step.rb +2 -2
  29. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_pricing_rules_step.rb +1 -2
  30. data/lib/3scale_toolbox/commands/plans_command/export/step.rb +8 -20
  31. data/lib/3scale_toolbox/commands/plans_command/import/import_plan_limits_step.rb +7 -8
  32. data/lib/3scale_toolbox/commands/plans_command/import/import_plan_metrics_step.rb +6 -13
  33. data/lib/3scale_toolbox/commands/plans_command/import/import_plan_pricing_rules_step.rb +8 -15
  34. data/lib/3scale_toolbox/commands/plans_command/import/step.rb +2 -22
  35. data/lib/3scale_toolbox/commands/plans_command/list_command.rb +1 -1
  36. data/lib/3scale_toolbox/commands/plans_command/show_command.rb +1 -1
  37. data/lib/3scale_toolbox/commands/policies_command.rb +24 -0
  38. data/lib/3scale_toolbox/commands/policies_command/export_command.rb +98 -0
  39. data/lib/3scale_toolbox/commands/policies_command/import_command.rb +61 -0
  40. data/lib/3scale_toolbox/commands/product_command.rb +4 -0
  41. data/lib/3scale_toolbox/commands/product_command/copy_command.rb +8 -3
  42. data/lib/3scale_toolbox/commands/product_command/copy_command/copy_backends_task.rb +28 -5
  43. data/lib/3scale_toolbox/commands/product_command/export_command.rb +81 -0
  44. data/lib/3scale_toolbox/commands/product_command/import_command.rb +126 -0
  45. data/lib/3scale_toolbox/commands/proxy_config_command.rb +2 -0
  46. data/lib/3scale_toolbox/commands/proxy_config_command/deploy_command.rb +54 -0
  47. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_activedocs_task.rb +15 -12
  48. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_app_plans_task.rb +15 -15
  49. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_limits_task.rb +12 -13
  50. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_mapping_rules_task.rb +11 -11
  51. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_methods_task.rb +9 -12
  52. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_metrics_task.rb +8 -8
  53. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_policies_task.rb +1 -1
  54. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_pricingrules_task.rb +15 -18
  55. data/lib/3scale_toolbox/commands/service_command/copy_command/copy_service_proxy_task.rb +2 -2
  56. data/lib/3scale_toolbox/commands/service_command/copy_command/create_or_update_service_task.rb +2 -1
  57. data/lib/3scale_toolbox/commands/service_command/copy_command/destroy_mapping_rules_task.rb +9 -5
  58. data/lib/3scale_toolbox/commands/service_command/copy_command/task.rb +20 -34
  59. data/lib/3scale_toolbox/commands/update_command/service_command/delete_activedocs_task.rb +1 -3
  60. data/lib/3scale_toolbox/crds.rb +16 -0
  61. data/lib/3scale_toolbox/crds/application_plan_dump.rb +19 -0
  62. data/lib/3scale_toolbox/crds/backend_dump.rb +39 -0
  63. data/lib/3scale_toolbox/crds/backend_mapping_rule_dump.rb +26 -0
  64. data/lib/3scale_toolbox/crds/backend_method_dump.rb +12 -0
  65. data/lib/3scale_toolbox/crds/backend_metric_dump.rb +13 -0
  66. data/lib/3scale_toolbox/crds/backend_parser.rb +55 -0
  67. data/lib/3scale_toolbox/crds/backend_usage_dump.rb +11 -0
  68. data/lib/3scale_toolbox/crds/limit_dump.rb +37 -0
  69. data/lib/3scale_toolbox/crds/mapping_rule_dump.rb +26 -0
  70. data/lib/3scale_toolbox/crds/method_dump.rb +12 -0
  71. data/lib/3scale_toolbox/crds/metric_dump.rb +13 -0
  72. data/lib/3scale_toolbox/crds/pricing_rule_dump.rb +38 -0
  73. data/lib/3scale_toolbox/crds/product_deployment_parser.rb +329 -0
  74. data/lib/3scale_toolbox/crds/product_dump.rb +157 -0
  75. data/lib/3scale_toolbox/crds/product_parser.rb +114 -0
  76. data/lib/3scale_toolbox/crds/remote.rb +682 -0
  77. data/lib/3scale_toolbox/entities.rb +3 -0
  78. data/lib/3scale_toolbox/entities/account.rb +1 -1
  79. data/lib/3scale_toolbox/entities/activedocs.rb +12 -0
  80. data/lib/3scale_toolbox/entities/application_plan.rb +54 -46
  81. data/lib/3scale_toolbox/entities/backend.rb +65 -30
  82. data/lib/3scale_toolbox/entities/backend_mapping_rule.rb +29 -3
  83. data/lib/3scale_toolbox/entities/backend_method.rb +25 -16
  84. data/lib/3scale_toolbox/entities/backend_metric.rb +12 -2
  85. data/lib/3scale_toolbox/entities/backend_usage.rb +7 -1
  86. data/lib/3scale_toolbox/entities/limit.rb +71 -0
  87. data/lib/3scale_toolbox/entities/mapping_rule.rb +90 -0
  88. data/lib/3scale_toolbox/entities/method.rb +33 -19
  89. data/lib/3scale_toolbox/entities/metric.rb +29 -18
  90. data/lib/3scale_toolbox/entities/pricing_rule.rb +63 -0
  91. data/lib/3scale_toolbox/entities/proxy_config.rb +0 -1
  92. data/lib/3scale_toolbox/entities/service.rb +149 -46
  93. data/lib/3scale_toolbox/helper.rb +13 -16
  94. data/lib/3scale_toolbox/proxy_logger.rb +4 -0
  95. data/lib/3scale_toolbox/remote_cache.rb +164 -0
  96. data/lib/3scale_toolbox/remotes.rb +2 -2
  97. data/lib/3scale_toolbox/version.rb +1 -1
  98. data/licenses.xml +98 -50
  99. metadata +37 -9
@@ -0,0 +1,157 @@
1
+ module ThreeScaleToolbox
2
+ module CRD
3
+ module ProductSerializer
4
+ def to_cr
5
+ {
6
+ 'apiVersion' => 'capabilities.3scale.net/v1beta1',
7
+ 'kind' => 'Product',
8
+ 'metadata' => {
9
+ 'annotations' => {
10
+ '3scale_toolbox_created_at' => Time.now.utc.iso8601,
11
+ '3scale_toolbox_version' => ThreeScaleToolbox::VERSION
12
+ },
13
+ 'name' => cr_name
14
+ },
15
+ 'spec' => {
16
+ 'name' => name,
17
+ 'systemName' => system_name,
18
+ 'description' => description,
19
+ 'mappingRules' => mapping_rules.map(&:to_cr),
20
+ 'metrics' => metrics.each_with_object({}) do |metric, hash|
21
+ hash[metric.system_name] = metric.to_cr
22
+ end,
23
+ 'methods' => methods.each_with_object({}) do |method, hash|
24
+ hash[method.system_name] = method.to_cr
25
+ end,
26
+ 'policies' => policies,
27
+ 'applicationPlans' => plans.each_with_object({}) do |app_plan, hash|
28
+ hash[app_plan.system_name] = app_plan.to_cr
29
+ end,
30
+ 'backendUsages' => backend_usage_list.each_with_object({}) do |backend_usage, hash|
31
+ hash[backend_usage.backend.system_name] = backend_usage.to_cr
32
+ end,
33
+ 'deployment' => deployment_to_cr
34
+ }
35
+ }
36
+ end
37
+
38
+ def cr_name
39
+ # Should be DNS1123 subdomain name
40
+ # TODO run validation for DNS1123
41
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/names/
42
+ "#{system_name.gsub(/[^[a-zA-Z0-9\-\.]]/, '.')}.#{Helper.random_lowercase_name}"
43
+ end
44
+
45
+ def deployment_to_cr
46
+ case deployment_option
47
+ when 'hosted'
48
+ hosted_deployment_to_cr
49
+ when 'self_managed'
50
+ self_managed_deployment_to_cr
51
+ else
52
+ raise ThreeScaleToolbox::Error, "Unknown deployment option: #{deployment_option}"
53
+ end
54
+ end
55
+
56
+ def hosted_deployment_to_cr
57
+ {
58
+ 'apicastHosted' => { 'authentication' => authentication_to_cr }
59
+ }
60
+ end
61
+
62
+ def self_managed_deployment_to_cr
63
+ {
64
+ 'apicastSelfManaged' => {
65
+ 'authentication' => authentication_to_cr,
66
+ 'stagingPublicBaseURL' => cached_proxy['sandbox_endpoint'],
67
+ 'productionPublicBaseURL' => cached_proxy['endpoint']
68
+ }
69
+ }
70
+ end
71
+
72
+ def authentication_to_cr
73
+ case backend_version
74
+ when '1'
75
+ userkey_authentication_to_cr
76
+ when '2'
77
+ appkey_authentication_to_cr
78
+ when 'oidc'
79
+ oidc_authentication_to_cr
80
+ else
81
+ raise ThreeScaleToolbox::Error, "Unknown backend_version: #{backend_version}"
82
+ end
83
+ end
84
+
85
+ def userkey_authentication_to_cr
86
+ {
87
+ 'userkey' => {
88
+ 'authUserKey' => cached_proxy['auth_user_key'],
89
+ 'credentials' => cached_proxy['credentials_location'],
90
+ 'security' => security_to_cr,
91
+ 'gatewayResponse' => gateway_response_to_cr
92
+ }
93
+ }
94
+ end
95
+
96
+ def appkey_authentication_to_cr
97
+ {
98
+ 'appKeyAppID' => {
99
+ 'appID' => cached_proxy['auth_app_id'],
100
+ 'appKey' => cached_proxy['auth_app_key'],
101
+ 'credentials' => cached_proxy['credentials_location'],
102
+ 'security' => security_to_cr,
103
+ 'gatewayResponse' => gateway_response_to_cr
104
+ }
105
+ }
106
+ end
107
+
108
+ def oidc_authentication_to_cr
109
+ {
110
+ 'oidc' => {
111
+ 'issuerType' => cached_proxy['oidc_issuer_type'],
112
+ 'issuerEndpoint' => cached_proxy['oidc_issuer_endpoint'],
113
+ 'jwtClaimWithClientID' => cached_proxy['jwt_claim_with_client_id'],
114
+ 'jwtClaimWithClientIDType' => cached_proxy['jwt_claim_with_client_id_type'],
115
+ 'authenticationFlow' => oidc_flow_to_cr,
116
+ 'credentials' => cached_proxy['credentials_location'],
117
+ 'security' => security_to_cr,
118
+ 'gatewayResponse' => gateway_response_to_cr
119
+ }
120
+ }
121
+ end
122
+
123
+ def oidc_flow_to_cr
124
+ {
125
+ 'standardFlowEnabled' => cached_oidc['standard_flow_enabled'],
126
+ 'implicitFlowEnabled' => cached_oidc['implicit_flow_enabled'],
127
+ 'serviceAccountsEnabled' => cached_oidc['service_accounts_enabled'],
128
+ 'directAccessGrantsEnabled' => cached_oidc['direct_access_grants_enabled']
129
+ }
130
+ end
131
+
132
+ def security_to_cr
133
+ {
134
+ 'hostHeader' => cached_proxy['hostname_rewrite'],
135
+ 'secretToken' => cached_proxy['secret_token']
136
+ }
137
+ end
138
+
139
+ def gateway_response_to_cr
140
+ {
141
+ 'errorStatusAuthFailed' => cached_proxy['error_status_auth_failed'],
142
+ 'errorHeadersAuthFailed' => cached_proxy['error_headers_auth_failed'],
143
+ 'errorAuthFailed' => cached_proxy['error_auth_failed'],
144
+ 'errorStatusAuthMissing' => cached_proxy['error_status_auth_missing'],
145
+ 'errorHeadersAuthMissing' => cached_proxy['error_headers_auth_missing'],
146
+ 'errorAuthMissing' => cached_proxy['error_auth_missing'],
147
+ 'errorStatusNoMatch' => cached_proxy['error_status_no_match'],
148
+ 'errorHeadersNoMatch' => cached_proxy['error_headers_no_match'],
149
+ 'errorNoMatch' => cached_proxy['error_no_match'],
150
+ 'errorStatusLimitsExceeded' => cached_proxy['error_status_limits_exceeded'],
151
+ 'errorHeadersLimitsExceeded' => cached_proxy['error_headers_limits_exceeded'],
152
+ 'errorLimitsExceeded' => cached_proxy['error_limits_exceeded']
153
+ }
154
+ end
155
+ end
156
+ end
157
+ end
@@ -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