3scale_toolbox 0.5.1 → 0.6.0

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +143 -23
  3. data/exe/3scale +10 -3
  4. data/lib/3scale_toolbox.rb +18 -0
  5. data/lib/3scale_toolbox/3scale_client_factory.rb +33 -0
  6. data/lib/3scale_toolbox/base_command.rb +52 -14
  7. data/lib/3scale_toolbox/cli.rb +26 -5
  8. data/lib/3scale_toolbox/cli/error_handler.rb +120 -0
  9. data/lib/3scale_toolbox/commands.rb +3 -9
  10. data/lib/3scale_toolbox/commands/3scale_command.rb +8 -6
  11. data/lib/3scale_toolbox/commands/copy_command.rb +4 -4
  12. data/lib/3scale_toolbox/commands/copy_command/copy_service.rb +40 -193
  13. data/lib/3scale_toolbox/commands/help_command.rb +1 -1
  14. data/lib/3scale_toolbox/commands/import_command.rb +6 -4
  15. data/lib/3scale_toolbox/commands/import_command/import_csv.rb +15 -41
  16. data/lib/3scale_toolbox/commands/import_command/openapi.rb +70 -0
  17. data/lib/3scale_toolbox/commands/import_command/openapi/create_mapping_rule_step.rb +18 -0
  18. data/lib/3scale_toolbox/commands/import_command/openapi/create_method_step.rb +39 -0
  19. data/lib/3scale_toolbox/commands/import_command/openapi/create_service_step.rb +69 -0
  20. data/lib/3scale_toolbox/commands/import_command/openapi/mapping_rule.rb +35 -0
  21. data/lib/3scale_toolbox/commands/import_command/openapi/method.rb +25 -0
  22. data/lib/3scale_toolbox/commands/import_command/openapi/operation.rb +22 -0
  23. data/lib/3scale_toolbox/commands/import_command/openapi/resource_reader.rb +49 -0
  24. data/lib/3scale_toolbox/commands/import_command/openapi/step.rb +45 -0
  25. data/lib/3scale_toolbox/commands/import_command/openapi/threescale_api_spec.rb +33 -0
  26. data/lib/3scale_toolbox/commands/remote_command.rb +36 -0
  27. data/lib/3scale_toolbox/commands/remote_command/remote_add.rb +47 -0
  28. data/lib/3scale_toolbox/commands/remote_command/remote_list.rb +29 -0
  29. data/lib/3scale_toolbox/commands/remote_command/remote_remove.rb +26 -0
  30. data/lib/3scale_toolbox/commands/remote_command/remote_rename.rb +42 -0
  31. data/lib/3scale_toolbox/commands/update_command.rb +4 -4
  32. data/lib/3scale_toolbox/commands/update_command/update_service.rb +45 -235
  33. data/lib/3scale_toolbox/configuration.rb +35 -0
  34. data/lib/3scale_toolbox/entities.rb +1 -0
  35. data/lib/3scale_toolbox/entities/service.rb +113 -0
  36. data/lib/3scale_toolbox/error.rb +8 -0
  37. data/lib/3scale_toolbox/helper.rb +37 -0
  38. data/lib/3scale_toolbox/remotes.rb +93 -0
  39. data/lib/3scale_toolbox/tasks.rb +10 -0
  40. data/lib/3scale_toolbox/tasks/copy_app_plans_task.rb +31 -0
  41. data/lib/3scale_toolbox/tasks/copy_limits_task.rb +36 -0
  42. data/lib/3scale_toolbox/tasks/copy_mapping_rules_task.rb +29 -0
  43. data/lib/3scale_toolbox/tasks/copy_methods_task.rb +29 -0
  44. data/lib/3scale_toolbox/tasks/copy_metrics_task.rb +33 -0
  45. data/lib/3scale_toolbox/tasks/copy_service_proxy_task.rb +12 -0
  46. data/lib/3scale_toolbox/tasks/copy_task.rb +26 -0
  47. data/lib/3scale_toolbox/tasks/destroy_mapping_rules_task.rb +22 -0
  48. data/lib/3scale_toolbox/tasks/helper_task.rb +25 -0
  49. data/lib/3scale_toolbox/tasks/update_service_settings_task.rb +32 -0
  50. data/lib/3scale_toolbox/version.rb +1 -1
  51. metadata +87 -11
@@ -0,0 +1,35 @@
1
+ require 'yaml/store'
2
+
3
+ module ThreeScaleToolbox
4
+ class Configuration
5
+ attr_reader :config_file
6
+
7
+ def initialize(config_file)
8
+ @config_file = config_file
9
+ @store = YAML::Store.new(config_file)
10
+ end
11
+
12
+ def data(key)
13
+ read[key]
14
+ end
15
+
16
+ def update(key)
17
+ return if key.nil?
18
+
19
+ @store.transaction do
20
+ @store[key] = yield @store[key]
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ # returns copy of data stored
27
+ def read
28
+ @store.transaction(true) do
29
+ @store.roots.each_with_object({}) do |key, obj|
30
+ obj[key] = @store[key]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1 @@
1
+ require '3scale_toolbox/entities/service'
@@ -0,0 +1,113 @@
1
+ module ThreeScaleToolbox
2
+ module Entities
3
+ class Service
4
+ VALID_PARAMS = %w[
5
+ name backend_version deployment_option description
6
+ system_name end_user_registration_required
7
+ support_email tech_support_email admin_support_email
8
+ ].freeze
9
+ public_constant :VALID_PARAMS
10
+
11
+ class << self
12
+ def create(remote:, service:, system_name:)
13
+ svc_obj = remote.create_service copy_service_params(service, system_name)
14
+ unless svc_obj['errors'].nil?
15
+ raise ThreeScaleToolbox::Error, 'Service has not been saved. ' \
16
+ "Errors: #{svc_obj['errors']}"
17
+ end
18
+
19
+ new(id: svc_obj.fetch('id'), remote: remote)
20
+ end
21
+
22
+ private
23
+
24
+ def copy_service_params(original, system_name)
25
+ service_params = Helper.filter_params(VALID_PARAMS, original)
26
+ service_params.tap do |hash|
27
+ hash['system_name'] = system_name if system_name
28
+ end
29
+ end
30
+ end
31
+
32
+ attr_reader :id, :remote
33
+
34
+ def initialize(id:, remote:)
35
+ @id = id
36
+ @remote = remote
37
+ end
38
+
39
+ def show_service
40
+ remote.show_service id
41
+ end
42
+
43
+ def update_proxy(proxy)
44
+ remote.update_proxy id, proxy
45
+ end
46
+
47
+ def show_proxy
48
+ remote.show_proxy id
49
+ end
50
+
51
+ def metrics
52
+ remote.list_metrics id
53
+ end
54
+
55
+ def hits
56
+ hits_metric = metrics.find do |metric|
57
+ metric['system_name'] == 'hits'
58
+ end
59
+ raise ThreeScaleToolbox::Error, 'missing hits metric' if hits_metric.nil?
60
+
61
+ hits_metric
62
+ end
63
+
64
+ def methods
65
+ remote.list_methods id, hits['id']
66
+ end
67
+
68
+ def create_metric(metric)
69
+ remote.create_metric id, metric
70
+ end
71
+
72
+ def create_method(parent_metric_id, method)
73
+ remote.create_method id, parent_metric_id, method
74
+ end
75
+
76
+ def plans
77
+ remote.list_service_application_plans id
78
+ end
79
+
80
+ def create_application_plan(plan)
81
+ remote.create_application_plan id, plan
82
+ end
83
+
84
+ def plan_limits(plan_id)
85
+ remote.list_application_plan_limits(plan_id)
86
+ end
87
+
88
+ def create_application_plan_limit(plan_id, metric_id, limit)
89
+ remote.create_application_plan_limit plan_id, metric_id, limit
90
+ end
91
+
92
+ def mapping_rules
93
+ remote.list_mapping_rules id
94
+ end
95
+
96
+ def delete_mapping_rule(rule_id)
97
+ remote.delete_mapping_rule(id, rule_id)
98
+ end
99
+
100
+ def create_mapping_rule(mapping_rule)
101
+ remote.create_mapping_rule id, mapping_rule
102
+ end
103
+
104
+ def update_service(params)
105
+ remote.update_service(id, params)
106
+ end
107
+
108
+ def delete_service
109
+ remote.delete_service id
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,8 @@
1
+ module ThreeScaleToolbox
2
+ # Generic error. Superclass for all specific errors.
3
+ class Error < ::StandardError
4
+ end
5
+
6
+ class InvalidUrlError < Error
7
+ end
8
+ end
@@ -0,0 +1,37 @@
1
+ module ThreeScaleToolbox
2
+ module Helper
3
+ class << self
4
+ def compare_hashes(first, second, keys)
5
+ keys.map { |key| first.fetch(key, nil) } == keys.map { |key| second.fetch(key, nil) }
6
+ end
7
+
8
+ ##
9
+ # Compute array difference with custom comparator
10
+ def array_difference(ary, other_ary)
11
+ ary.reject do |ary_elem|
12
+ other_ary.find do |other_ary_elem|
13
+ yield(ary_elem, other_ary_elem)
14
+ end
15
+ end
16
+ end
17
+
18
+ ##
19
+ # Returns new hash object with not nil valid params
20
+ def filter_params(valid_params, source)
21
+ valid_params.each_with_object({}) do |key, target|
22
+ target[key] = source[key] unless source[key].nil?
23
+ end
24
+ end
25
+
26
+ def parse_uri(uri)
27
+ # raises error when remote_str is not string, but object or something else.
28
+ uri_obj = URI(uri)
29
+ # URI::HTTP is parent of URI::HTTPS
30
+ # with single check both types are checked
31
+ raise ThreeScaleToolbox::InvalidUrlError, "invalid url: #{uri}" unless uri_obj.kind_of?(URI::HTTP)
32
+
33
+ uri_obj
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,93 @@
1
+ module ThreeScaleToolbox
2
+ class Remotes
3
+ class << self
4
+ def from_uri(uri_str)
5
+ uri = Helper.parse_uri(uri_str)
6
+
7
+ authentication = uri.user
8
+ uri.user = ''
9
+ { authentication: authentication, endpoint: uri.to_s }
10
+ end
11
+ end
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ end
16
+
17
+ ##
18
+ # Fetch remotes
19
+ # Perform validation
20
+ #
21
+ def all
22
+ rmts = (config.data :remotes) || {}
23
+ raise_invalid unless validate(rmts)
24
+ rmts
25
+ end
26
+
27
+ def add_uri(name, uri)
28
+ remote = self.class.from_uri(uri)
29
+ add(name, remote)
30
+ end
31
+
32
+ def add(key, remote)
33
+ update do |rmts|
34
+ rmts.tap { |r| r[key] = remote }
35
+ end
36
+ end
37
+
38
+ def delete(key)
39
+ value = nil
40
+ update do |rmts|
41
+ # block should return rmts
42
+ # but main method should return deleted value
43
+ rmts.tap do |r|
44
+ value = if block_given?
45
+ r.delete(key, &Proc.new)
46
+ else
47
+ r.delete(key)
48
+ end
49
+ end
50
+ end
51
+ value
52
+ end
53
+
54
+ def fetch(name)
55
+ all.fetch name
56
+ end
57
+
58
+ private
59
+
60
+ attr_reader :config
61
+
62
+ ##
63
+ # Update remotes
64
+ # Perform validation
65
+ #
66
+ def update
67
+ config.update(:remotes) do |rmts|
68
+ yield(rmts || {}).tap do |new_rmts|
69
+ raise_invalid unless validate(new_rmts)
70
+ end
71
+ end
72
+ end
73
+
74
+ def raise_not_found(remote_str)
75
+ raise ThreeScaleToolbox::Error, "remote '#{remote_str}' not found from config file #{config.config_file}"
76
+ end
77
+
78
+ def raise_invalid
79
+ raise ThreeScaleToolbox::Error, "invalid remote configuration from config file #{config.config_file}"
80
+ end
81
+
82
+ def valid?(remote)
83
+ remote.is_a?(Hash) && remote.key?(:endpoint) && remote.key?(:authentication)
84
+ end
85
+
86
+ def validate(remotes)
87
+ case remotes
88
+ when Hash then remotes.values.all?(&method(:valid?))
89
+ else false
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,10 @@
1
+ require '3scale_toolbox/tasks/copy_task'
2
+ require '3scale_toolbox/tasks/helper_task'
3
+ require '3scale_toolbox/tasks/copy_service_proxy_task'
4
+ require '3scale_toolbox/tasks/copy_metrics_task'
5
+ require '3scale_toolbox/tasks/copy_methods_task'
6
+ require '3scale_toolbox/tasks/copy_app_plans_task'
7
+ require '3scale_toolbox/tasks/copy_limits_task'
8
+ require '3scale_toolbox/tasks/destroy_mapping_rules_task'
9
+ require '3scale_toolbox/tasks/copy_mapping_rules_task'
10
+ require '3scale_toolbox/tasks/update_service_settings_task'
@@ -0,0 +1,31 @@
1
+ module ThreeScaleToolbox
2
+ module Tasks
3
+ class CopyApplicationPlansTask
4
+ include CopyTask
5
+
6
+ def call
7
+ source_plans = source.plans
8
+ target_plans = target.plans
9
+ missing_plans = missing_app_plans(source_plans, target_plans)
10
+ missing_plans.each do |plan|
11
+ plan.delete('links')
12
+ plan.delete('default') # TODO: handle default plan
13
+ if plan.delete('custom') # TODO: what to do with custom plans?
14
+ puts "skipping custom plan #{plan}"
15
+ else
16
+ target.create_application_plan(plan)
17
+ end
18
+ end
19
+ puts "target service missing #{missing_plans.size} application plans"
20
+ end
21
+
22
+ private
23
+
24
+ def missing_app_plans(source_plans, target_plans)
25
+ ThreeScaleToolbox::Helper.array_difference(source_plans, target_plans) do |src, target|
26
+ ThreeScaleToolbox::Helper.compare_hashes(src, target, ['system_name'])
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ module ThreeScaleToolbox
2
+ module Tasks
3
+ class CopyLimitsTask
4
+ include CopyTask
5
+ include Helper
6
+
7
+ def call
8
+ metrics_map = metrics_mapping(source.metrics, target.metrics)
9
+ plan_mapping = application_plan_mapping(source.plans, target.plans)
10
+ plan_mapping.each do |plan_id, target_plan|
11
+ limits = source.plan_limits(plan_id)
12
+ limits_target = target.plan_limits(target_plan['id'])
13
+ missing_limits = missing_limits(limits, limits_target)
14
+ missing_limits.each do |limit|
15
+ limit.delete('links')
16
+ target.create_application_plan_limit(
17
+ target_plan['id'],
18
+ metrics_map.fetch(limit.fetch('metric_id')),
19
+ limit
20
+ )
21
+ end
22
+ puts "target application plan #{target_plan['id']} is missing" \
23
+ " #{missing_limits.size} from the original plan #{plan_id}"
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def missing_limits(source_limits, target_limits)
30
+ ThreeScaleToolbox::Helper.array_difference(source_limits, target_limits) do |limit, target|
31
+ ThreeScaleToolbox::Helper.compare_hashes(limit, target, ['period'])
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ module ThreeScaleToolbox
2
+ module Tasks
3
+ class CopyMappingRulesTask
4
+ include CopyTask
5
+ include Helper
6
+
7
+ def call
8
+ metrics_map = metrics_mapping(source.metrics, target.metrics)
9
+ missing_rules = missing_mapping_rules(source.mapping_rules,
10
+ target.mapping_rules, metrics_map)
11
+ missing_rules.each do |mapping_rule|
12
+ mapping_rule.delete('links')
13
+ mapping_rule['metric_id'] = metrics_map.fetch(mapping_rule.delete('metric_id'))
14
+ target.create_mapping_rule mapping_rule
15
+ end
16
+ puts "created #{missing_rules.size} mapping rules"
17
+ end
18
+
19
+ private
20
+
21
+ def missing_mapping_rules(source_rules, target_rules, metrics_map)
22
+ ThreeScaleToolbox::Helper.array_difference(source_rules, target_rules) do |source_rule, target_rule|
23
+ ThreeScaleToolbox::Helper.compare_hashes(source_rule, target_rule, %w[pattern http_method delta]) &&
24
+ metrics_map.fetch(source_rule.fetch('metric_id')) == target_rule.fetch('metric_id')
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module ThreeScaleToolbox
2
+ module Tasks
3
+ class CopyMethodsTask
4
+ include CopyTask
5
+
6
+ def call
7
+ source_methods = source.methods
8
+ target_methods = target.methods
9
+ target_hits_metric_id = target.hits['id']
10
+ puts "original service hits metric #{source.hits['id']} has #{source_methods.size} methods"
11
+ puts "target service hits metric #{target_hits_metric_id} has #{target_methods.size} methods"
12
+ missing = missing_methods(source_methods, target_methods).each do |method|
13
+ filtered_method = ThreeScaleToolbox::Helper.filter_params(%w[friendly_name system_name],
14
+ method)
15
+ target.create_method(target_hits_metric_id, filtered_method)
16
+ end
17
+ puts "created #{missing.size} missing methods on target service"
18
+ end
19
+
20
+ private
21
+
22
+ def missing_methods(source_methods, target_methods)
23
+ ThreeScaleToolbox::Helper.array_difference(source_methods, target_methods) do |method, target|
24
+ ThreeScaleToolbox::Helper.compare_hashes(method, target, ['system_name'])
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ module ThreeScaleToolbox
2
+ module Tasks
3
+ class CopyMetricsTask
4
+ include CopyTask
5
+
6
+ def call
7
+ source_metrics = source.metrics
8
+ target_metrics = target.metrics
9
+
10
+ puts "original service has #{source_metrics.size} metrics"
11
+ puts "target service has #{target_metrics.size} metrics"
12
+
13
+ missing = missing_metrics(source_metrics, target_metrics)
14
+
15
+ missing.each do |metric|
16
+ metric.delete('links')
17
+ target.create_metric(metric)
18
+ end
19
+
20
+ puts "created #{missing.size} metrics on the target service"
21
+ end
22
+
23
+ private
24
+
25
+ def missing_metrics(source_metrics, target_metrics)
26
+ ThreeScaleToolbox::Helper.array_difference(source_metrics,
27
+ target_metrics) do |source, target|
28
+ ThreeScaleToolbox::Helper.compare_hashes(source, target, ['system_name'])
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end