3scale_toolbox 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c06fef14e4b85f8c23bfbde952198345f502744e
4
- data.tar.gz: efd42927ca217afa27625810d1fecab9c23c5316
2
+ SHA256:
3
+ metadata.gz: 1faca82c3c88cb4312fa76704e70d656a400f53c3aecb03c507e8a1aeb374284
4
+ data.tar.gz: 32c28726d94b60b8238720b1ffe055b06b4ecb1aab3945fd069b899d19f12595
5
5
  SHA512:
6
- metadata.gz: 3926f07e4057c625aaff424c16a8721eaa639205abfe5f1d104c78cc9b6da537751289744faab53607e9c4635b2f6b8c3903d007afdc7e7c5aa550c11cf0b5ae
7
- data.tar.gz: 6010129bbd8a4a8ce8026ad98dcfaf0749133514be63baccd2c1f9da7b427ef6e74edfebb90cda81e5090f40afedcfbd46ffba6a9907865fb6020b7b0833a18f
6
+ metadata.gz: 720b320e9e20edecdb93256c2a804ffaff13593f180960e2e10027d5c915c94094a7c2bbf2aca9c9a3b78fc7e9c993bcfc3151d4ac61eab8bf0113a63ed0d5d5
7
+ data.tar.gz: c27a4f1aea1076ad163437cdf0b0082763a6f49fa10bcd2045fe63047ea08dc3ba0258833f8fcd8015a8de4604909c9ba7cf9b477b55511999a4c3544dcab5a2
data/exe/3scale-copy CHANGED
@@ -60,6 +60,7 @@ def copy_service_params(original, system_name)
60
60
  {
61
61
  name: original['name'],
62
62
  system_name: system_name,
63
+ backend_version: original['backend_version'],
63
64
  end_user_registration_required: original['end_user_registration_required']
64
65
  }.reject { |key, value| !value }
65
66
  end
data/exe/3scale-update ADDED
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require '3scale_toolbox/cli'
4
+ require 'optparse'
5
+ require '3scale/api'
6
+ require 'uri'
7
+
8
+ options = {}
9
+
10
+ parser = OptionParser.new do |parser|
11
+ parser.banner = '3scale update <command> [options]'
12
+
13
+ parser.on('-s', '--source SOURCE', "Source") do |domain|
14
+ options[:source] = domain
15
+ end
16
+
17
+ parser.on('-d', '--destination DESTINATION', "Destination") do |domain|
18
+ options[:destination] = domain
19
+ end
20
+
21
+ parser.on('-f', '--force', 'Overwrites the mapping rules by deleting all rules from target service first') do
22
+ options[:force] = true
23
+ end
24
+
25
+ # TODO: parametrize what parts of service need to be copied
26
+ parser.on('-r', '--rules-only', 'Updates only the mapping rules') do
27
+ options[:rules_only] = true
28
+ end
29
+
30
+ parser.on('-h', '--help', 'Prints this help') do
31
+ puts parser
32
+ puts
33
+ puts 'Available Commands:', ['service <source_service_id> <destination_service_id>', 'help']
34
+ exit
35
+ end
36
+ end
37
+
38
+ print_help = ->(error = nil) do
39
+ if error
40
+ puts "Error: #{error}"
41
+ puts
42
+ end
43
+ parser.parse(['--help'])
44
+ end
45
+
46
+ parser.parse!
47
+
48
+ def fetch_option(options, key)
49
+ options.fetch(key) { raise OptionParser::MissingArgument, key }
50
+ end
51
+
52
+ class ServiceUpdater
53
+
54
+ attr_reader :source_client, :target_client, :source_service_id, :target_service_id
55
+
56
+ def initialize (source, source_service_id, destination, target_service_id)
57
+ @source_client = ThreeScale::API.new(
58
+ endpoint: endpoint_from_url(source),
59
+ provider_key: provider_key_from_url(source)
60
+ )
61
+ @target_client = ThreeScale::API.new(
62
+ endpoint: endpoint_from_url(destination),
63
+ provider_key: provider_key_from_url(destination)
64
+ )
65
+ @source_service_id = source_service_id
66
+ @target_service_id = target_service_id
67
+ end
68
+
69
+ def compare_hashes(first, second, keys)
70
+ keys.map{ |key| first.fetch(key) } == keys.map{ |key| second.fetch(key) }
71
+ end
72
+
73
+ def provider_key_from_url(url)
74
+ URI(url).user
75
+ end
76
+
77
+ def endpoint_from_url(url)
78
+ uri = URI(url)
79
+ uri.user = nil
80
+
81
+ uri.to_s
82
+ end
83
+
84
+ def target_service_params(source)
85
+ # NOTE: backend_version and deployment_option are not yet returned by show_service method
86
+ params = %w(name backend_version deployment_option end_user_registration_required)
87
+ source.select { |k,v| params.include?(k) && v }
88
+ end
89
+
90
+ def source_metrics
91
+ @source_metrics ||= source_client.list_metrics(source_service_id)
92
+ end
93
+
94
+ def metrics_mapping
95
+ @metrics_mapping ||= target_client.list_metrics(target_service_id).map do |target|
96
+ metric = source_metrics.find{|metric| metric.fetch('system_name') == target.fetch('system_name') }
97
+ metric ||= {}
98
+
99
+ [metric['id'], target['id']]
100
+ end.to_h
101
+ end
102
+
103
+ def copy_service_settings
104
+ source_service = source_client.show_service(source_service_id)
105
+ puts "updating service settings for service id #{target_service_id}..."
106
+ target_update_response = target_client.update_service(target_service_id, target_service_params(source_service))
107
+ raise "Service has not been saved. Errors: #{target_update_response['errors']}" unless target_update_response['errors'].nil?
108
+ end
109
+
110
+ def copy_proxy_settings
111
+ puts "updating proxy configuration for service id #{target_service_id}..."
112
+ proxy = source_client.show_proxy(source_service_id)
113
+ target_client.update_proxy(target_service_id, proxy)
114
+ puts "updated proxy of #{target_service_id} to match the source #{source_service_id}"
115
+ end
116
+
117
+ def copy_metrics_and_methods
118
+ target_metrics = target_client.list_metrics(target_service_id)
119
+
120
+ source_hits = source_metrics.find{ |metric| metric['system_name'] == 'hits' } or raise 'missing hits metric'
121
+ target_hits = target_metrics.find{ |metric| metric['system_name'] == 'hits' } or raise 'missing hits metric'
122
+
123
+ source_methods = source_client.list_methods(source_service_id, source_hits['id'])
124
+ target_methods = target_client.list_methods(target_service_id, target_hits['id'])
125
+
126
+ puts "source service hits metric #{source_hits['id']} has #{source_methods.size} methods"
127
+ puts "target service hits metric #{target_hits['id']} has #{target_methods.size} methods"
128
+
129
+ missing_methods = source_methods.reject { |source_method| target_methods.find{|target_method| compare_hashes(source_method, target_method, ['system_name']) } }
130
+
131
+ puts "creating #{missing_methods.size} missing methods on target service..."
132
+ missing_methods.each do |method|
133
+ target = { friendly_name: method['friendly_name'], system_name: method['system_name'] }
134
+ target_client.create_method(target_service_id, target_hits['id'], target)
135
+ end
136
+
137
+ target_metrics = target_client.list_metrics(target_service_id)
138
+
139
+ puts "source service has #{source_metrics.size} metrics"
140
+ puts "target service has #{target_metrics.size} metrics"
141
+
142
+ missing_metrics = source_metrics.reject { |source_metric| target_metrics.find{|target_metric| compare_hashes(source_metric, target_metric, ['system_name']) } }
143
+
144
+ missing_metrics.map do |metric|
145
+ metric.delete('links')
146
+ target_client.create_metric(target_service_id, metric)
147
+ end
148
+
149
+ puts "created #{missing_metrics.size} metrics on the target service"
150
+ end
151
+
152
+ def copy_application_plans
153
+ source_plans = source_client.list_service_application_plans(source_service_id)
154
+ target_plans = target_client.list_service_application_plans(target_service_id)
155
+
156
+ puts "source service has #{source_plans.size} application plans"
157
+ puts "target service has #{target_plans.size} application plans"
158
+
159
+ missing_application_plans = source_plans.reject { |source_plan| target_plans.find{|target_plan| source_plan.fetch('system_name') == target_plan.fetch('system_name') } }
160
+
161
+ puts "creating #{missing_application_plans.size} missing application plans..."
162
+
163
+ missing_application_plans.each do |plan|
164
+ plan.delete('links')
165
+ plan.delete('default') # TODO: handle default plans
166
+
167
+ if plan.delete('custom') # TODO: what to do with custom plans?
168
+ puts "skipping custom plan #{plan}"
169
+ else
170
+ target_client.create_application_plan(target_service_id, plan)
171
+ end
172
+ end
173
+
174
+ puts "updating limits for application plans..."
175
+
176
+ application_plan_mapping = target_client.list_service_application_plans(target_service_id).map do |plan_target|
177
+ plan = source_plans.find{|plan| plan.fetch('system_name') == plan_target.fetch('system_name') }
178
+ plan ||= {}
179
+ [plan['id'], plan_target['id']]
180
+ end.to_h.reject { |key, value| !key }
181
+
182
+ application_plan_mapping.each do |source_id, target_id|
183
+ source_limits = source_client.list_application_plan_limits(source_id)
184
+ target_limits = target_client.list_application_plan_limits(target_id)
185
+
186
+ missing_limits = source_limits.reject { |limit| target_limits.find{|limit_target| limit.fetch('period') == limit_target.fetch('period') } }
187
+
188
+ puts "target application plan #{target_id} is missing #{missing_limits.size} from the source plan #{source_id}"
189
+
190
+ missing_limits.each do |limit|
191
+ limit.delete('links')
192
+ target_client.create_application_plan_limit(target_id, metrics_mapping.fetch(limit.fetch('metric_id')), limit)
193
+ end
194
+
195
+ end
196
+ end
197
+
198
+ def copy_mapping_rules force_mapping_rules
199
+ source_mapping_rules = source_client.list_mapping_rules(source_service_id)
200
+ target_mapping_rules = target_client.list_mapping_rules(target_service_id)
201
+
202
+ puts "the source service has #{source_mapping_rules.size} mapping rules"
203
+ puts "the target has #{target_mapping_rules.size} mapping rules"
204
+
205
+ if force_mapping_rules
206
+ puts "force mode was chosen, deleting existing mapping rules on target service..."
207
+ target_mapping_rules.each do |rule|
208
+ target_client.delete_mapping_rule(target_service_id, rule['id'])
209
+ end
210
+ missing_mapping_rules = source_mapping_rules
211
+ else
212
+ unique_target_mapping_rules = target_mapping_rules.dup
213
+
214
+ missing_mapping_rules = source_mapping_rules.reject do |mapping_rule|
215
+ matching_metric = unique_target_mapping_rules.find do |target|
216
+ compare_hashes(mapping_rule, target, %w(pattern http_method delta)) &&
217
+ metrics_mapping.fetch(mapping_rule.fetch('metric_id')) == target.fetch('metric_id')
218
+ end
219
+
220
+ unique_target_mapping_rules.delete(matching_metric)
221
+ end
222
+ end
223
+
224
+ puts "missing #{missing_mapping_rules.size} mapping rules"
225
+
226
+ missing_mapping_rules.each do |mapping_rule|
227
+ mapping_rule.delete('links')
228
+ mapping_rule['metric_id'] = metrics_mapping.fetch(mapping_rule.delete('metric_id'))
229
+ target_client.create_mapping_rule(target_service_id, mapping_rule)
230
+ end
231
+ puts "created #{missing_mapping_rules.size} mapping rules"
232
+ end
233
+
234
+ def update_service force_mapping_rules=false
235
+ copy_service_settings
236
+ copy_proxy_settings
237
+ copy_metrics_and_methods
238
+ copy_application_plans
239
+ copy_mapping_rules force_mapping_rules
240
+ end
241
+ end
242
+
243
+ case (command = ARGV.shift)
244
+ when 'service'
245
+ source = fetch_option options, :source
246
+ destination = fetch_option options, :destination
247
+
248
+ rules_only = options[:rules_only] == true
249
+ force_update = options[:force] == true
250
+
251
+ source_service_id = ARGV.shift or raise OptionParser::MissingArgument, 'source_service_id'
252
+ target_service_id = ARGV.shift or raise OptionParser::MissingArgument, 'destination_service_id'
253
+
254
+ updater = ServiceUpdater.new(source, source_service_id, destination, target_service_id)
255
+
256
+ if rules_only
257
+ updater.copy_mapping_rules force_update
258
+ else
259
+ updater.update_service force_update
260
+ end
261
+
262
+ when 'help'
263
+ print_help.call
264
+ when nil
265
+ print_help.call("missing subcommand")
266
+ exit 1
267
+ else
268
+ print_help.call("unknown command #{command}")
269
+ exit 1
270
+ end
@@ -1,3 +1,3 @@
1
1
  module ThreeScaleToolbox
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: 3scale_toolbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Cichra
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-14 00:00:00.000000000 Z
11
+ date: 2018-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -60,6 +60,7 @@ executables:
60
60
  - 3scale-copy
61
61
  - 3scale-help
62
62
  - 3scale-import
63
+ - 3scale-update
63
64
  extensions: []
64
65
  extra_rdoc_files: []
65
66
  files:
@@ -68,6 +69,7 @@ files:
68
69
  - exe/3scale-copy
69
70
  - exe/3scale-help
70
71
  - exe/3scale-import
72
+ - exe/3scale-update
71
73
  - lib/3scale_toolbox.rb
72
74
  - lib/3scale_toolbox/cli.rb
73
75
  - lib/3scale_toolbox/version.rb
@@ -91,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
93
  version: '0'
92
94
  requirements: []
93
95
  rubyforge_project:
94
- rubygems_version: 2.6.12
96
+ rubygems_version: 2.7.3
95
97
  signing_key:
96
98
  specification_version: 4
97
99
  summary: 3scale CLI Toolbox.