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 +5 -5
- data/exe/3scale-copy +1 -0
- data/exe/3scale-update +270 -0
- data/lib/3scale_toolbox/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1faca82c3c88cb4312fa76704e70d656a400f53c3aecb03c507e8a1aeb374284
|
4
|
+
data.tar.gz: 32c28726d94b60b8238720b1ffe055b06b4ecb1aab3945fd069b899d19f12595
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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.
|
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:
|
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.
|
96
|
+
rubygems_version: 2.7.3
|
95
97
|
signing_key:
|
96
98
|
specification_version: 4
|
97
99
|
summary: 3scale CLI Toolbox.
|