3scale_toolbox 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +143 -23
- data/exe/3scale +10 -3
- data/lib/3scale_toolbox.rb +18 -0
- data/lib/3scale_toolbox/3scale_client_factory.rb +33 -0
- data/lib/3scale_toolbox/base_command.rb +52 -14
- data/lib/3scale_toolbox/cli.rb +26 -5
- data/lib/3scale_toolbox/cli/error_handler.rb +120 -0
- data/lib/3scale_toolbox/commands.rb +3 -9
- data/lib/3scale_toolbox/commands/3scale_command.rb +8 -6
- data/lib/3scale_toolbox/commands/copy_command.rb +4 -4
- data/lib/3scale_toolbox/commands/copy_command/copy_service.rb +40 -193
- data/lib/3scale_toolbox/commands/help_command.rb +1 -1
- data/lib/3scale_toolbox/commands/import_command.rb +6 -4
- data/lib/3scale_toolbox/commands/import_command/import_csv.rb +15 -41
- data/lib/3scale_toolbox/commands/import_command/openapi.rb +70 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/create_mapping_rule_step.rb +18 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/create_method_step.rb +39 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/create_service_step.rb +69 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/mapping_rule.rb +35 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/method.rb +25 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/operation.rb +22 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/resource_reader.rb +49 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/step.rb +45 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/threescale_api_spec.rb +33 -0
- data/lib/3scale_toolbox/commands/remote_command.rb +36 -0
- data/lib/3scale_toolbox/commands/remote_command/remote_add.rb +47 -0
- data/lib/3scale_toolbox/commands/remote_command/remote_list.rb +29 -0
- data/lib/3scale_toolbox/commands/remote_command/remote_remove.rb +26 -0
- data/lib/3scale_toolbox/commands/remote_command/remote_rename.rb +42 -0
- data/lib/3scale_toolbox/commands/update_command.rb +4 -4
- data/lib/3scale_toolbox/commands/update_command/update_service.rb +45 -235
- data/lib/3scale_toolbox/configuration.rb +35 -0
- data/lib/3scale_toolbox/entities.rb +1 -0
- data/lib/3scale_toolbox/entities/service.rb +113 -0
- data/lib/3scale_toolbox/error.rb +8 -0
- data/lib/3scale_toolbox/helper.rb +37 -0
- data/lib/3scale_toolbox/remotes.rb +93 -0
- data/lib/3scale_toolbox/tasks.rb +10 -0
- data/lib/3scale_toolbox/tasks/copy_app_plans_task.rb +31 -0
- data/lib/3scale_toolbox/tasks/copy_limits_task.rb +36 -0
- data/lib/3scale_toolbox/tasks/copy_mapping_rules_task.rb +29 -0
- data/lib/3scale_toolbox/tasks/copy_methods_task.rb +29 -0
- data/lib/3scale_toolbox/tasks/copy_metrics_task.rb +33 -0
- data/lib/3scale_toolbox/tasks/copy_service_proxy_task.rb +12 -0
- data/lib/3scale_toolbox/tasks/copy_task.rb +26 -0
- data/lib/3scale_toolbox/tasks/destroy_mapping_rules_task.rb +22 -0
- data/lib/3scale_toolbox/tasks/helper_task.rb +25 -0
- data/lib/3scale_toolbox/tasks/update_service_settings_task.rb +32 -0
- data/lib/3scale_toolbox/version.rb +1 -1
- metadata +87 -11
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module ThreeScaleToolbox
|
4
|
+
module CLI
|
5
|
+
class ErrorHandler
|
6
|
+
def self.error_watchdog
|
7
|
+
new.error_watchdog { yield }
|
8
|
+
end
|
9
|
+
|
10
|
+
# Catches errors and prints nice diagnostic messages
|
11
|
+
def error_watchdog
|
12
|
+
# Run
|
13
|
+
yield
|
14
|
+
rescue StandardError, ScriptError => e
|
15
|
+
handle_error e
|
16
|
+
e
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def handle_error(error)
|
24
|
+
if expected_error?(error)
|
25
|
+
warn
|
26
|
+
warn "\e[1m\e[31mError: #{error.message}\e[0m"
|
27
|
+
else
|
28
|
+
print_error(error)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def expected_error?(error)
|
33
|
+
case error
|
34
|
+
when ThreeScaleToolbox::Error
|
35
|
+
true
|
36
|
+
else
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def print_error(error)
|
42
|
+
write_error(error, $stderr)
|
43
|
+
|
44
|
+
File.open('crash.log', 'w') do |io|
|
45
|
+
write_verbose_error(error, io)
|
46
|
+
end
|
47
|
+
|
48
|
+
write_section_header($stderr, 'Detailed information')
|
49
|
+
warn
|
50
|
+
warn 'A detailed crash log has been written to ./crash.log.'
|
51
|
+
end
|
52
|
+
|
53
|
+
def write_error(error, stream)
|
54
|
+
write_error_message(error, stream)
|
55
|
+
write_stack_trace(error, stream)
|
56
|
+
end
|
57
|
+
|
58
|
+
def write_error_message(error, stream)
|
59
|
+
write_section_header(stream, 'Message')
|
60
|
+
stream.puts "\e[1m\e[31m#{error.class}: #{error.message}\e[0m"
|
61
|
+
end
|
62
|
+
|
63
|
+
def write_stack_trace(error, stream)
|
64
|
+
write_section_header(stream, 'Backtrace')
|
65
|
+
stream.puts error.backtrace
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_version_information(stream)
|
69
|
+
write_section_header(stream, 'Version Information')
|
70
|
+
stream.puts ThreeScaleToolbox::VERSION
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_system_information(stream)
|
74
|
+
write_section_header(stream, 'System Information')
|
75
|
+
stream.puts Etc.uname.to_json
|
76
|
+
end
|
77
|
+
|
78
|
+
def write_installed_gems(stream)
|
79
|
+
write_section_header(stream, 'Installed gems')
|
80
|
+
gems_and_versions.each do |g|
|
81
|
+
stream.puts " #{g.first} #{g.last.join(', ')}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def write_load_paths(stream)
|
86
|
+
write_section_header(stream, 'Load paths')
|
87
|
+
$LOAD_PATH.each_with_index do |i, index|
|
88
|
+
stream.puts " #{index}. #{i}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def write_verbose_error(error, stream)
|
93
|
+
stream.puts "Crashlog created at #{Time.now}"
|
94
|
+
|
95
|
+
write_error_message(error, stream)
|
96
|
+
write_stack_trace(error, stream)
|
97
|
+
write_version_information(stream)
|
98
|
+
write_system_information(stream)
|
99
|
+
write_installed_gems(stream)
|
100
|
+
write_load_paths(stream)
|
101
|
+
end
|
102
|
+
|
103
|
+
def gems_and_versions
|
104
|
+
gems = {}
|
105
|
+
Gem::Specification.find_all.sort_by { |s| [s.name, s.version] }.each do |spec|
|
106
|
+
gems[spec.name] ||= []
|
107
|
+
gems[spec.name] << spec.version.to_s
|
108
|
+
end
|
109
|
+
gems
|
110
|
+
end
|
111
|
+
|
112
|
+
def write_section_header(stream, title)
|
113
|
+
stream.puts
|
114
|
+
|
115
|
+
stream.puts "===== #{title.upcase}:"
|
116
|
+
stream.puts
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -3,6 +3,7 @@ require '3scale_toolbox/commands/help_command'
|
|
3
3
|
require '3scale_toolbox/commands/copy_command'
|
4
4
|
require '3scale_toolbox/commands/import_command'
|
5
5
|
require '3scale_toolbox/commands/update_command'
|
6
|
+
require '3scale_toolbox/commands/remote_command'
|
6
7
|
|
7
8
|
module ThreeScaleToolbox
|
8
9
|
module Commands
|
@@ -10,15 +11,8 @@ module ThreeScaleToolbox
|
|
10
11
|
ThreeScaleToolbox::Commands::HelpCommand,
|
11
12
|
ThreeScaleToolbox::Commands::CopyCommand,
|
12
13
|
ThreeScaleToolbox::Commands::ImportCommand,
|
13
|
-
ThreeScaleToolbox::Commands::UpdateCommand
|
14
|
+
ThreeScaleToolbox::Commands::UpdateCommand,
|
15
|
+
ThreeScaleToolbox::Commands::RemoteCommand::RemoteCommand
|
14
16
|
].freeze
|
15
|
-
|
16
|
-
def self.service_valid_params
|
17
|
-
%w[
|
18
|
-
name backend_version deployment_option description
|
19
|
-
system_name end_user_registration_required
|
20
|
-
support_email tech_support_email admin_support_email
|
21
|
-
]
|
22
|
-
end
|
23
17
|
end
|
24
18
|
end
|
@@ -5,16 +5,18 @@ require '3scale_toolbox/base_command'
|
|
5
5
|
module ThreeScaleToolbox
|
6
6
|
module Commands
|
7
7
|
module ThreeScaleCommand
|
8
|
-
|
8
|
+
include ThreeScaleToolbox::Command
|
9
9
|
def self.command
|
10
10
|
Cri::Command.define do
|
11
11
|
name '3scale'
|
12
|
-
usage '3scale <command> [options]'
|
13
|
-
summary '3scale
|
14
|
-
description '3scale
|
15
|
-
|
12
|
+
usage '3scale <sub-command> [options]'
|
13
|
+
summary '3scale toolbox'
|
14
|
+
description '3scale toolbox to manage your API from the terminal.'
|
15
|
+
option :c, 'config-file', '3scale toolbox configuration file',
|
16
|
+
argument: :required, default: ThreeScaleToolbox.default_config_file
|
17
|
+
flag :v, :version, 'Prints the version of this command' do
|
16
18
|
puts ThreeScaleToolbox::VERSION
|
17
|
-
exit
|
19
|
+
exit 0
|
18
20
|
end
|
19
21
|
flag :k, :insecure, 'Proceed and operate even for server connections otherwise considered insecure'
|
20
22
|
flag :h, :help, 'show help for this command' do |_, cmd|
|
@@ -5,13 +5,13 @@ require '3scale_toolbox/commands/copy_command/copy_service'
|
|
5
5
|
module ThreeScaleToolbox
|
6
6
|
module Commands
|
7
7
|
module CopyCommand
|
8
|
-
|
8
|
+
include ThreeScaleToolbox::Command
|
9
9
|
def self.command
|
10
10
|
Cri::Command.define do
|
11
11
|
name 'copy'
|
12
|
-
usage 'copy <command> [options]'
|
13
|
-
summary '
|
14
|
-
description '3scale
|
12
|
+
usage 'copy <sub-command> [options]'
|
13
|
+
summary 'copy super command'
|
14
|
+
description 'Copy 3scale entities between tenants'
|
15
15
|
end
|
16
16
|
end
|
17
17
|
add_subcommand(CopyServiceSubcommand)
|
@@ -4,213 +4,60 @@ require '3scale_toolbox/base_command'
|
|
4
4
|
module ThreeScaleToolbox
|
5
5
|
module Commands
|
6
6
|
module CopyCommand
|
7
|
-
|
8
|
-
|
7
|
+
class CopyServiceSubcommand < Cri::CommandRunner
|
8
|
+
include ThreeScaleToolbox::Command
|
9
|
+
|
9
10
|
def self.command
|
10
11
|
Cri::Command.define do
|
11
12
|
name 'service'
|
12
13
|
usage 'service [opts] -s <src> -d <dst> <service_id>'
|
13
|
-
summary '
|
14
|
-
description '
|
14
|
+
summary 'copy service'
|
15
|
+
description 'will create a new services, copy existing proxy settings, metrics, methods, application plans and mapping rules.'
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
option :s, :source, '3scale source instance. Url or remote name', argument: :required
|
18
|
+
option :d, :destination, '3scale target instance. Url or remote name', argument: :required
|
19
|
+
option :t, 'target_system_name', 'Target system name', argument: :required
|
20
|
+
param :service_id
|
19
21
|
|
20
|
-
|
21
|
-
CopyServiceSubcommand.run opts, args
|
22
|
-
end
|
22
|
+
runner CopyServiceSubcommand
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
source = fetch_required_option(
|
28
|
-
destination = fetch_required_option(
|
29
|
-
system_name = fetch_required_option(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
keys.map{ |key| first.fetch(key) } == keys.map{ |key| second.fetch(key) }
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.provider_key_from_url(url)
|
50
|
-
url[/\w*@/][0..-2]
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.endpoint_from_url(url)
|
54
|
-
url.sub /\w*@/, ''
|
26
|
+
def run
|
27
|
+
source = fetch_required_option(:source)
|
28
|
+
destination = fetch_required_option(:destination)
|
29
|
+
system_name = fetch_required_option(:target_system_name)
|
30
|
+
|
31
|
+
source_service = Entities::Service.new(id: arguments[:service_id],
|
32
|
+
remote: threescale_client(source))
|
33
|
+
target_service = create_new_service(source_service.show_service, destination, system_name)
|
34
|
+
puts "new service id #{target_service.id}"
|
35
|
+
context = create_context(source_service, target_service)
|
36
|
+
tasks = [
|
37
|
+
Tasks::CopyServiceProxyTask.new(context),
|
38
|
+
Tasks::CopyMethodsTask.new(context),
|
39
|
+
Tasks::CopyMetricsTask.new(context),
|
40
|
+
Tasks::CopyApplicationPlansTask.new(context),
|
41
|
+
Tasks::CopyLimitsTask.new(context),
|
42
|
+
Tasks::DestroyMappingRulesTask.new(context),
|
43
|
+
Tasks::CopyMappingRulesTask.new(context)
|
44
|
+
]
|
45
|
+
tasks.each(&:call)
|
55
46
|
end
|
56
47
|
|
48
|
+
private
|
57
49
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
target
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.copy_service_params(original, system_name)
|
66
|
-
service_params = filter_params(Commands.service_valid_params, original)
|
67
|
-
service_params.tap do |hash|
|
68
|
-
hash['system_name'] = system_name if system_name
|
69
|
-
end
|
50
|
+
def create_context(source, target)
|
51
|
+
{
|
52
|
+
source: source,
|
53
|
+
target: target
|
54
|
+
}
|
70
55
|
end
|
71
56
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
endpoint: endpoint_from_url(source),
|
77
|
-
provider_key: provider_key_from_url(source),
|
78
|
-
verify_ssl: !insecure
|
79
|
-
)
|
80
|
-
client = ThreeScale::API.new(
|
81
|
-
endpoint: endpoint_from_url(destination),
|
82
|
-
provider_key: provider_key_from_url(destination),
|
83
|
-
verify_ssl: !insecure
|
84
|
-
)
|
85
|
-
|
86
|
-
service = source_client.show_service(service_id)
|
87
|
-
copy = client.create_service(copy_service_params(service, system_name))
|
88
|
-
|
89
|
-
raise "Service has not been saved. Errors: #{copy['errors']}" unless copy['errors'].nil?
|
90
|
-
|
91
|
-
service_copy_id = copy.fetch('id')
|
92
|
-
|
93
|
-
puts "new service id #{service_copy_id}"
|
94
|
-
|
95
|
-
proxy = source_client.show_proxy(service_id)
|
96
|
-
client.update_proxy(service_copy_id, proxy)
|
97
|
-
puts "updated proxy of #{service_copy_id} to match the original"
|
98
|
-
|
99
|
-
metrics = source_client.list_metrics(service_id)
|
100
|
-
metrics_copies = client.list_metrics(service_copy_id)
|
101
|
-
|
102
|
-
hits = metrics.find{ |metric| metric['system_name'] == 'hits' } or raise 'missing hits metric'
|
103
|
-
hits_copy = metrics_copies.find{ |metric| metric['system_name'] == 'hits' } or raise 'missing hits metric'
|
104
|
-
|
105
|
-
methods = source_client.list_methods(service_id, hits['id'])
|
106
|
-
methods_copies = client.list_methods(service_copy_id, hits_copy['id'])
|
107
|
-
|
108
|
-
puts "original service hits metric #{hits['id']} has #{methods.size} methods"
|
109
|
-
puts "copied service hits metric #{hits_copy['id']} has #{methods_copies.size} methods"
|
110
|
-
|
111
|
-
missing_methods = methods.reject { |method| methods_copies.find{|copy| compare_hashes(method, copy, ['system_name']) } }
|
112
|
-
|
113
|
-
puts "creating #{missing_methods.size} missing methods on copied service"
|
114
|
-
|
115
|
-
missing_methods.each do |method|
|
116
|
-
copy = { friendly_name: method['friendly_name'], system_name: method['system_name'] }
|
117
|
-
client.create_method(service_copy_id, hits_copy['id'], copy)
|
118
|
-
end
|
119
|
-
|
120
|
-
metrics_copies = client.list_metrics(service_copy_id)
|
121
|
-
|
122
|
-
puts "original service has #{metrics.size} metrics"
|
123
|
-
puts "copied service has #{metrics_copies.size} metrics"
|
124
|
-
|
125
|
-
missing_metrics = metrics.reject { |metric| metrics_copies.find{|copy| compare_hashes(metric, copy, ['system_name']) } }
|
126
|
-
|
127
|
-
missing_metrics.map do |metric|
|
128
|
-
metric.delete('links')
|
129
|
-
client.create_metric(service_copy_id, metric)
|
130
|
-
end
|
131
|
-
|
132
|
-
puts "created #{missing_metrics.size} metrics on the copied service"
|
133
|
-
|
134
|
-
plans = source_client.list_service_application_plans(service_id)
|
135
|
-
plan_copies = client.list_service_application_plans(service_copy_id)
|
136
|
-
|
137
|
-
puts "original service has #{plans.size} application plans "
|
138
|
-
puts "copied service has #{plan_copies.size} application plans"
|
139
|
-
|
140
|
-
missing_application_plans = plans.reject { |plan| plan_copies.find{|copy| plan.fetch('system_name') == copy.fetch('system_name') } }
|
141
|
-
|
142
|
-
puts "copied service missing #{missing_application_plans.size} application plans"
|
143
|
-
|
144
|
-
missing_application_plans.each do |plan|
|
145
|
-
plan.delete('links')
|
146
|
-
plan.delete('default') # TODO: handle default plan
|
147
|
-
|
148
|
-
if plan.delete('custom') # TODO: what to do with custom plans?
|
149
|
-
puts "skipping custom plan #{plan}"
|
150
|
-
else
|
151
|
-
client.create_application_plan(service_copy_id, plan)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
application_plan_mapping = client.list_service_application_plans(service_copy_id).map do |plan_copy|
|
156
|
-
plan = plans.find{|plan| plan.fetch('system_name') == plan_copy.fetch('system_name') }
|
157
|
-
|
158
|
-
[plan['id'], plan_copy['id']]
|
159
|
-
end
|
160
|
-
|
161
|
-
metrics_mapping = client.list_metrics(service_copy_id).map do |copy|
|
162
|
-
metric = metrics.find{|metric| metric.fetch('system_name') == copy.fetch('system_name') }
|
163
|
-
metric ||= {}
|
164
|
-
|
165
|
-
[metric['id'], copy['id']]
|
166
|
-
end.to_h
|
167
|
-
|
168
|
-
puts "destroying all mapping rules of the copy which have been created by default"
|
169
|
-
client.list_mapping_rules(service_copy_id).each do |mapping_rule|
|
170
|
-
client.delete_mapping_rule(service_copy_id, mapping_rule['id'])
|
171
|
-
end
|
172
|
-
|
173
|
-
mapping_rules = source_client.list_mapping_rules(service_id)
|
174
|
-
mapping_rules_copy = client.list_mapping_rules(service_copy_id)
|
175
|
-
|
176
|
-
puts "the original service has #{mapping_rules.size} mapping rules"
|
177
|
-
puts "the copy has #{mapping_rules_copy.size} mapping rules"
|
178
|
-
|
179
|
-
unique_mapping_rules_copy = mapping_rules_copy.dup
|
180
|
-
|
181
|
-
missing_mapping_rules = mapping_rules.reject do |mapping_rule|
|
182
|
-
matching_metric = unique_mapping_rules_copy.find do |copy|
|
183
|
-
compare_hashes(mapping_rule, copy, %w(pattern http_method delta)) &&
|
184
|
-
metrics_mapping.fetch(mapping_rule.fetch('metric_id')) == copy.fetch('metric_id')
|
185
|
-
end
|
186
|
-
|
187
|
-
unique_mapping_rules_copy.delete(matching_metric)
|
188
|
-
end
|
189
|
-
|
190
|
-
puts "missing #{missing_mapping_rules.size} mapping rules"
|
191
|
-
|
192
|
-
missing_mapping_rules.each do |mapping_rule|
|
193
|
-
mapping_rule.delete('links')
|
194
|
-
mapping_rule['metric_id'] = metrics_mapping.fetch(mapping_rule.delete('metric_id'))
|
195
|
-
client.create_mapping_rule(service_copy_id, mapping_rule)
|
196
|
-
end
|
197
|
-
puts "created #{missing_mapping_rules.size} mapping rules"
|
198
|
-
|
199
|
-
puts "extra #{unique_mapping_rules_copy.size} mapping rules"
|
200
|
-
puts unique_mapping_rules_copy.each{|rule| rule.delete('links') }
|
201
|
-
|
202
|
-
application_plan_mapping.each do |original_id, copy_id|
|
203
|
-
limits = source_client.list_application_plan_limits(original_id)
|
204
|
-
limits_copy = client.list_application_plan_limits(copy_id)
|
205
|
-
|
206
|
-
missing_limits = limits.reject { |limit| limits_copy.find{|limit_copy| limit.fetch('period') == limit_copy.fetch('period') } }
|
207
|
-
|
208
|
-
missing_limits.each do |limit|
|
209
|
-
limit.delete('links')
|
210
|
-
client.create_application_plan_limit(copy_id, metrics_mapping.fetch(limit.fetch('metric_id')), limit)
|
211
|
-
end
|
212
|
-
puts "copied application plan #{copy_id} is missing #{missing_limits.size} from the original plan #{original_id}"
|
213
|
-
end
|
57
|
+
def create_new_service(service, destination, system_name)
|
58
|
+
Entities::Service.create(remote: threescale_client(destination),
|
59
|
+
service: service,
|
60
|
+
system_name: system_name)
|
214
61
|
end
|
215
62
|
end
|
216
63
|
end
|