3scale_toolbox 0.11.0 → 0.12.2

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/3scale_toolbox.gemspec +42 -0
  3. data/LICENSE +201 -0
  4. data/NOTICE +15 -0
  5. data/README.md +8 -0
  6. data/lib/3scale_toolbox.rb +1 -0
  7. data/lib/3scale_toolbox/commands/application_command/apply_command.rb +21 -6
  8. data/lib/3scale_toolbox/commands/application_command/create_command.rb +3 -1
  9. data/lib/3scale_toolbox/commands/copy_command/copy_service.rb +5 -0
  10. data/lib/3scale_toolbox/commands/import_command/openapi.rb +5 -3
  11. data/lib/3scale_toolbox/commands/import_command/openapi/create_activedocs_step.rb +10 -2
  12. data/lib/3scale_toolbox/commands/import_command/openapi/create_method_step.rb +13 -6
  13. data/lib/3scale_toolbox/commands/import_command/openapi/method.rb +5 -0
  14. data/lib/3scale_toolbox/commands/import_command/openapi/step.rb +4 -0
  15. data/lib/3scale_toolbox/commands/import_command/openapi/threescale_api_spec.rb +2 -1
  16. data/lib/3scale_toolbox/commands/import_command/openapi/update_service_proxy_step.rb +12 -5
  17. data/lib/3scale_toolbox/commands/metrics_command/list_command.rb +1 -8
  18. data/lib/3scale_toolbox/commands/plans_command/apply_command.rb +8 -7
  19. data/lib/3scale_toolbox/commands/plans_command/create_command.rb +0 -2
  20. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_methods_step.rb +0 -3
  21. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_metrics_step.rb +0 -2
  22. data/lib/3scale_toolbox/commands/plans_command/export/read_plan_pricing_rules_step.rb +2 -1
  23. data/lib/3scale_toolbox/commands/plans_command/export/step.rb +0 -2
  24. data/lib/3scale_toolbox/commands/plans_command/import/create_or_update_app_plan_step.rb +8 -3
  25. data/lib/3scale_toolbox/commands/plans_command/import/import_plan_metrics_step.rb +0 -3
  26. data/lib/3scale_toolbox/commands/plans_command/import/import_plan_pricing_rules_step.rb +9 -2
  27. data/lib/3scale_toolbox/commands/plans_command/import/step.rb +5 -1
  28. data/lib/3scale_toolbox/commands/proxy_config_command/promote_command.rb +21 -5
  29. data/lib/3scale_toolbox/entities/account.rb +44 -4
  30. data/lib/3scale_toolbox/entities/activedocs.rb +1 -1
  31. data/lib/3scale_toolbox/entities/application.rb +21 -13
  32. data/lib/3scale_toolbox/entities/application_plan.rb +13 -12
  33. data/lib/3scale_toolbox/entities/base_entity.rb +1 -1
  34. data/lib/3scale_toolbox/entities/method.rb +1 -1
  35. data/lib/3scale_toolbox/entities/metric.rb +1 -1
  36. data/lib/3scale_toolbox/entities/service.rb +30 -15
  37. data/lib/3scale_toolbox/remotes.rb +1 -1
  38. data/lib/3scale_toolbox/resource_reader.rb +5 -2
  39. data/lib/3scale_toolbox/swagger/swagger.rb +5 -3
  40. data/lib/3scale_toolbox/tasks/copy_activedocs_task.rb +27 -9
  41. data/lib/3scale_toolbox/tasks/copy_limits_task.rb +4 -1
  42. data/lib/3scale_toolbox/tasks/copy_mapping_rules_task.rb +4 -1
  43. data/lib/3scale_toolbox/tasks/copy_methods_task.rb +20 -16
  44. data/lib/3scale_toolbox/tasks/copy_metrics_task.rb +14 -14
  45. data/lib/3scale_toolbox/tasks/copy_pricingrules_task.rb +4 -1
  46. data/lib/3scale_toolbox/tasks/copy_task.rb +40 -0
  47. data/lib/3scale_toolbox/version.rb +1 -1
  48. data/licenses.xml +303 -0
  49. metadata +8 -4
@@ -42,7 +42,7 @@ module ThreeScaleToolbox
42
42
  attr_reader :id, :remote
43
43
 
44
44
  def initialize(id:, remote:, attrs: nil)
45
- @id = id
45
+ @id = id.to_i
46
46
  @remote = remote
47
47
  @attrs = attrs
48
48
  end
@@ -16,37 +16,37 @@ module ThreeScaleToolbox
16
16
  # * User_key (API key)
17
17
  # * App_id (from app_id/app_key pair)
18
18
  # * Client ID (for OAuth and OpenID Connect authentication modes)
19
- def find(remote:, ref:)
20
- app = find_by_user_key(remote, ref)
19
+ def find(remote:, service_id: nil, ref:)
20
+ app = find_by_user_key(remote, service_id, ref)
21
21
  return app unless app.nil?
22
22
 
23
- app = find_by_app_id(remote, ref)
23
+ app = find_by_app_id(remote, service_id, ref)
24
24
  return app unless app.nil?
25
25
 
26
- app = find_by_id(remote, ref)
26
+ app = find_by_id(remote, service_id, ref)
27
27
  return app unless app.nil?
28
28
 
29
29
  nil
30
30
  end
31
31
 
32
- def find_by_id(remote, id)
33
- generic_find(remote, :id, id)
32
+ def find_by_id(remote, service_id, id)
33
+ generic_find(remote, service_id, :id, id)
34
34
  end
35
35
 
36
- def find_by_user_key(remote, user_key)
37
- generic_find(remote, :user_key, user_key)
36
+ def find_by_user_key(remote, service_id, user_key)
37
+ generic_find(remote, service_id, :user_key, user_key)
38
38
  end
39
39
 
40
- def find_by_app_id(remote, app_id)
41
- generic_find(remote, :application_id, app_id)
40
+ def find_by_app_id(remote, service_id, app_id)
41
+ generic_find(remote, service_id, :application_id, app_id)
42
42
  end
43
43
 
44
44
  private
45
45
 
46
- def generic_find(remote, type, ref)
46
+ def generic_find(remote, service_id, type, ref)
47
47
  # find_application criteria only accepts one parameter.
48
48
  # Otherwise unexpected behavior
49
- attrs = remote.find_application(type => ref)
49
+ attrs = remote.find_application(service_id: service_id, type => ref)
50
50
  if (errors = attrs['errors'])
51
51
  raise ThreeScaleToolbox::ThreeScaleApiError.new('Application find error', errors)
52
52
  end
@@ -60,7 +60,7 @@ module ThreeScaleToolbox
60
60
  attr_reader :id, :remote
61
61
 
62
62
  def initialize(id:, remote:, attrs: nil)
63
- @id = id
63
+ @id = id.to_i
64
64
  @remote = remote
65
65
  @attrs = attrs
66
66
  end
@@ -81,6 +81,8 @@ module ThreeScaleToolbox
81
81
  end
82
82
 
83
83
  def resume
84
+ return attrs if live?
85
+
84
86
  new_attrs = remote.resume_application(account_id, id)
85
87
  if (errors = new_attrs['errors'])
86
88
  raise ThreeScaleToolbox::ThreeScaleApiError.new('Application has not been resumed', errors)
@@ -92,6 +94,8 @@ module ThreeScaleToolbox
92
94
  end
93
95
 
94
96
  def suspend
97
+ return attrs unless live?
98
+
95
99
  new_attrs = remote.suspend_application(account_id, id)
96
100
  if (errors = new_attrs['errors'])
97
101
  raise ThreeScaleToolbox::ThreeScaleApiError.new('Application has not been suspended', errors)
@@ -106,6 +110,10 @@ module ThreeScaleToolbox
106
110
  remote.delete_application account_id, id
107
111
  end
108
112
 
113
+ def live?
114
+ attrs.fetch('state') == 'live'
115
+ end
116
+
109
117
  private
110
118
 
111
119
  def account_id
@@ -39,7 +39,7 @@ module ThreeScaleToolbox
39
39
  attr_reader :id, :service, :remote
40
40
 
41
41
  def initialize(id:, service:, attrs: nil)
42
- @id = id
42
+ @id = id.to_i
43
43
  @service = service
44
44
  @remote = service.remote
45
45
  @attrs = attrs
@@ -50,6 +50,8 @@ module ThreeScaleToolbox
50
50
  end
51
51
 
52
52
  def update(plan_attrs)
53
+ return attrs if update_plan_attrs(plan_attrs).empty?
54
+
53
55
  new_attrs = remote.update_application_plan(service.id, id,
54
56
  update_plan_attrs(plan_attrs))
55
57
  if (errors = new_attrs['errors'])
@@ -170,6 +172,10 @@ module ThreeScaleToolbox
170
172
  end
171
173
  end
172
174
 
175
+ def published?
176
+ attrs.fetch('state') == 'published'
177
+ end
178
+
173
179
  private
174
180
 
175
181
  def read_plan_attrs
@@ -181,17 +187,12 @@ module ThreeScaleToolbox
181
187
  plan_attrs
182
188
  end
183
189
 
184
- def update_plan_attrs(source_attrs)
185
- # shallow copy is enough
186
- source_attrs.clone.tap do |new_plan_attrs|
187
- new_plan_attrs.delete('id')
188
- new_plan_attrs.delete('links')
189
- new_plan_attrs.delete('system_name')
190
- # plans are created by default in hidden state
191
- # If published is required, 'state_event' attr has to be added
192
- state = new_plan_attrs.delete('state')
193
- new_plan_attrs['state_event'] = 'publish' if state == 'published'
194
- new_plan_attrs['state_event'] = 'hide' if state == 'hidden'
190
+ def update_plan_attrs(update_attrs)
191
+ new_attrs = update_attrs.reject { |key, _| %w[id links system_name].include? key }
192
+ new_attrs.tap do |params|
193
+ state = params.delete('state')
194
+ params['state_event'] = 'publish' if state == 'published' && !published?
195
+ params['state_event'] = 'hide' if state == 'hidden' && published?
195
196
  end
196
197
  end
197
198
 
@@ -16,7 +16,7 @@ module ThreeScaleToolbox
16
16
  attr_reader :id, :attrs, :remote
17
17
 
18
18
  def initialize(id:, remote:, attrs: nil, verbose: false)
19
- @id = id
19
+ @id = id.to_i
20
20
  @remote = remote
21
21
  @attrs = attrs
22
22
  @verbose = verbose
@@ -30,7 +30,7 @@ module ThreeScaleToolbox
30
30
  attr_reader :id, :parent_id, :service, :remote
31
31
 
32
32
  def initialize(id:, parent_id:, service:, attrs: nil)
33
- @id = id
33
+ @id = id.to_i
34
34
  @service = service
35
35
  @parent_id = parent_id
36
36
  @remote = service.remote
@@ -29,7 +29,7 @@ module ThreeScaleToolbox
29
29
  attr_reader :id, :service, :remote
30
30
 
31
31
  def initialize(id:, service:, attrs: nil)
32
- @id = id
32
+ @id = id.to_i
33
33
  @service = service
34
34
  @remote = service.remote
35
35
  @attrs = attrs
@@ -66,7 +66,7 @@ module ThreeScaleToolbox
66
66
  attr_reader :id, :remote
67
67
 
68
68
  def initialize(id:, remote:, attrs: nil)
69
- @id = id
69
+ @id = id.to_i
70
70
  @remote = remote
71
71
  @attrs = attrs
72
72
  end
@@ -95,21 +95,18 @@ module ThreeScaleToolbox
95
95
  end
96
96
 
97
97
  def metrics
98
- service_metrics = remote.list_metrics id
99
- if service_metrics.respond_to?(:has_key?) && (errors = service_metrics['errors'])
100
- raise ThreeScaleToolbox::ThreeScaleApiError.new('Service metrics not read', errors)
101
- end
98
+ # cache result to reuse
99
+ metric_and_method_list = metrics_and_methods
100
+ hits_metric_obj = hits_metric(metric_and_method_list)
101
+ hits_id = hits_metric_obj.fetch('id')
102
102
 
103
- service_metrics
103
+ ThreeScaleToolbox::Helper.array_difference(metric_and_method_list, methods(hits_id)) do |metric, method|
104
+ ThreeScaleToolbox::Helper.compare_hashes(metric, method, %w[id])
105
+ end
104
106
  end
105
107
 
106
108
  def hits
107
- hits_metric = metrics.find do |metric|
108
- metric['system_name'] == 'hits'
109
- end
110
- raise ThreeScaleToolbox::Error, 'missing hits metric' if hits_metric.nil?
111
-
112
- hits_metric
109
+ hits_metric(metrics_and_methods)
113
110
  end
114
111
 
115
112
  def methods(parent_metric_id)
@@ -121,6 +118,15 @@ module ThreeScaleToolbox
121
118
  service_methods
122
119
  end
123
120
 
121
+ def metrics_and_methods
122
+ m_m = remote.list_metrics id
123
+ if m_m.respond_to?(:has_key?) && (errors = m_m['errors'])
124
+ raise ThreeScaleToolbox::ThreeScaleApiError.new('Service metrics not read', errors)
125
+ end
126
+
127
+ m_m
128
+ end
129
+
124
130
  def plans
125
131
  service_plans = remote.list_service_application_plans id
126
132
  if service_plans.respond_to?(:has_key?) && (errors = service_plans['errors'])
@@ -174,9 +180,7 @@ module ThreeScaleToolbox
174
180
  end
175
181
 
176
182
  tenant_activedocs.select do |activedoc|
177
- # service_id is optional attr. It would return nil and would not match
178
- # activedocs endpoints return service_id as integers
179
- activedoc['service_id'] == id.to_i
183
+ activedoc['service_id'] == id
180
184
  end
181
185
  end
182
186
 
@@ -245,8 +249,19 @@ module ThreeScaleToolbox
245
249
  end
246
250
  end
247
251
 
252
+ def ==(other)
253
+ remote.http_client.endpoint == other.remote.http_client.endpoint && id == other.id
254
+ end
255
+
248
256
  private
249
257
 
258
+ def hits_metric(metric_list)
259
+ hits_metric = metric_list.find { |metric| metric['system_name'] == 'hits' }
260
+ raise ThreeScaleToolbox::Error, 'missing hits metric' if hits_metric.nil?
261
+
262
+ hits_metric
263
+ end
264
+
250
265
  def service_attrs
251
266
  svc = remote.show_service id
252
267
  if (errors = svc['errors'])
@@ -52,7 +52,7 @@ module ThreeScaleToolbox
52
52
  end
53
53
 
54
54
  def fetch(name)
55
- all.fetch name
55
+ all.fetch(name) { raise_not_found(name) }
56
56
  end
57
57
 
58
58
  private
@@ -27,8 +27,11 @@ module ThreeScaleToolbox
27
27
  end
28
28
 
29
29
  # Detect format from file extension
30
- def read_file(resource)
31
- File.read(resource)
30
+ def read_file(filename)
31
+ raise ThreeScaleToolbox::Error, "File not found: #{filename} " unless File.file?(filename)
32
+ raise ThreeScaleToolbox::Error, "File not readable: #{filename} " unless File.readable?(filename)
33
+
34
+ File.read(filename)
32
35
  end
33
36
 
34
37
  def read_stdin(_resource)
@@ -23,12 +23,13 @@ module ThreeScaleToolbox
23
23
  end
24
24
 
25
25
  class Operation
26
- attr_reader :verb, :operation_id, :path
26
+ attr_reader :verb, :operation_id, :path, :description
27
27
 
28
- def initialize(verb:, operation_id:, path:)
28
+ def initialize(verb:, operation_id:, path:, description:)
29
29
  @verb = verb
30
30
  @operation_id = operation_id
31
31
  @path = path
32
+ @description = description
32
33
  end
33
34
  end
34
35
 
@@ -83,7 +84,8 @@ module ThreeScaleToolbox
83
84
  path_obj.flat_map do |method, operation|
84
85
  next unless %w[get head post put patch delete trace options].include? method
85
86
 
86
- Operation.new(verb: method, path: path, operation_id: operation['operationId'])
87
+ Operation.new(verb: method, path: path, description: operation['description'],
88
+ operation_id: operation['operationId'])
87
89
  end.compact
88
90
  end
89
91
  end
@@ -6,17 +6,35 @@ module ThreeScaleToolbox
6
6
  def call
7
7
  puts 'copying all service ActiveDocs'
8
8
 
9
- target_activedocs = source.activedocs.map do |source_activedoc|
10
- source_activedoc.clone.tap do |target_activedoc|
11
- target_activedoc.delete('id')
12
- target_activedoc['service_id'] = target.id
13
- target_activedoc['system_name'] = "#{target_activedoc['system_name']}#{target.id}"
14
- end
9
+ source.activedocs.each(&method(:apply_target_activedoc))
10
+ end
11
+
12
+ private
13
+
14
+ def apply_target_activedoc(attrs)
15
+ activedocs = Entities::ActiveDocs.find_by_system_name(remote: target.remote,
16
+ system_name: attrs['system_name'])
17
+ if activedocs.nil?
18
+ Entities::ActiveDocs.create(remote: target.remote, attrs: create_attrs(attrs))
19
+ elsif activedocs.attrs.fetch('service_id') == target.id
20
+ activedocs.update(update_attrs(attrs))
21
+ else
22
+ # activedocs with same system_name exists, but now owned by target service
23
+ new_attrs = create_attrs(attrs)
24
+ new_attrs['system_name'] = "#{attrs['system_name']}#{target.id}"
25
+ Entities::ActiveDocs.create(remote: target.remote, attrs: new_attrs)
15
26
  end
27
+ end
28
+
29
+ def update_attrs(old_attrs)
30
+ create_attrs(old_attrs)
31
+ end
16
32
 
17
- target_activedocs.each do |ad|
18
- res = target.remote.create_activedocs(ad)
19
- raise ThreeScaleToolbox::Error, "ActiveDocs has not been created. Errors: #{res['errors']}" unless res['errors'].nil?
33
+ def create_attrs(old_attrs)
34
+ # keep same system_name
35
+ new_attrs = old_attrs.reject { |key, _| %w[id created_at updated_at].include? key }
36
+ new_attrs.tap do |attrs|
37
+ attrs['service_id'] = target.id
20
38
  end
21
39
  end
22
40
  end
@@ -5,7 +5,6 @@ module ThreeScaleToolbox
5
5
  include Helper
6
6
 
7
7
  def call
8
- metrics_map = metrics_mapping(source.metrics, target.metrics)
9
8
  plan_mapping = application_plan_mapping(source.plans, target.plans)
10
9
  plan_mapping.each do |plan_id, target_plan|
11
10
  source_plan = ThreeScaleToolbox::Entities::ApplicationPlan.new(id: plan_id, service: source)
@@ -22,6 +21,10 @@ module ThreeScaleToolbox
22
21
 
23
22
  private
24
23
 
24
+ def metrics_map
25
+ @metrics_map ||= metrics_mapping(source_metrics_and_methods, target_metrics_and_methods)
26
+ end
27
+
25
28
  def missing_limits(source_limits, target_limits, metrics_map)
26
29
  ThreeScaleToolbox::Helper.array_difference(source_limits, target_limits) do |limit, target|
27
30
  ThreeScaleToolbox::Helper.compare_hashes(limit, target, ['period']) &&
@@ -5,7 +5,6 @@ module ThreeScaleToolbox
5
5
  include Helper
6
6
 
7
7
  def call
8
- metrics_map = metrics_mapping(source.metrics, target.metrics)
9
8
  missing_rules = missing_mapping_rules(source.mapping_rules,
10
9
  target.mapping_rules, metrics_map)
11
10
  missing_rules.each do |mapping_rule|
@@ -18,6 +17,10 @@ module ThreeScaleToolbox
18
17
 
19
18
  private
20
19
 
20
+ def metrics_map
21
+ @metrics_map ||= metrics_mapping(source_metrics_and_methods, target_metrics_and_methods)
22
+ end
23
+
21
24
  def missing_mapping_rules(source_rules, target_rules, metrics_map)
22
25
  ThreeScaleToolbox::Helper.array_difference(source_rules, target_rules) do |source_rule, target_rule|
23
26
  ThreeScaleToolbox::Helper.compare_hashes(source_rule, target_rule, %w[pattern http_method delta]) &&
@@ -4,26 +4,30 @@ module ThreeScaleToolbox
4
4
  include CopyTask
5
5
 
6
6
  def call
7
- source_hits_id = source.hits['id']
8
- target_hits_id = target.hits['id']
9
- source_methods = source.methods source_hits_id
10
- target_methods = target.methods target_hits_id
11
- puts "original service hits metric #{source_hits_id} has #{source_methods.size} methods"
12
- puts "target service hits metric #{target_hits_id} has #{target_methods.size} methods"
13
- missing = missing_methods(source_methods, target_methods).each do |method|
14
- Entities::Method.create(
15
- service: target,
16
- parent_id: target_hits_id,
17
- attrs: ThreeScaleToolbox::Helper.filter_params(%w[friendly_name system_name], method)
18
- )
19
- end
20
- puts "created #{missing.size} missing methods on target service"
7
+ puts "original service hits metric #{source_hits.fetch('id')} has #{source_methods.size} methods"
8
+ puts "target service hits metric #{target_hits.fetch('id')} has #{target_methods.size} methods"
9
+ missing_methods.each(&method(:create_method))
10
+ puts "created #{missing_methods.size} missing methods on target service"
11
+ invalidate_target_methods if missing_methods.size.positive?
21
12
  end
22
13
 
23
14
  private
24
15
 
25
- def missing_methods(source_methods, target_methods)
26
- ThreeScaleToolbox::Helper.array_difference(source_methods, target_methods) do |method, target|
16
+ def create_method(method)
17
+ Entities::Method.create(
18
+ service: target,
19
+ parent_id: target_hits.fetch('id'),
20
+ attrs: ThreeScaleToolbox::Helper.filter_params(%w[friendly_name system_name], method)
21
+ )
22
+ rescue ThreeScaleToolbox::ThreeScaleApiError => e
23
+ raise e unless ThreeScaleToolbox::Helper.system_name_already_taken_error?(e.apierrors)
24
+
25
+ warn "[WARN] method #{method.fetch('system_name')} not created. " \
26
+ 'Metric with the same system_name exists.'
27
+ end
28
+
29
+ def missing_methods
30
+ @missing_methods ||= ThreeScaleToolbox::Helper.array_difference(source_methods, target_methods) do |method, target|
27
31
  ThreeScaleToolbox::Helper.compare_hashes(method, target, ['system_name'])
28
32
  end
29
33
  end