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.
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,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
- extend ThreeScaleToolbox::Command
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 CLI Toolbox'
14
- description '3scale CLI tools to manage your API from the terminal.'
15
- flag :v, :version, 'Prints the version of this command' do |_, _|
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
- extend ThreeScaleToolbox::Command
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 '3scale copy command'
14
- description '3scale copy command.'
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
- module CopyServiceSubcommand
8
- extend ThreeScaleToolbox::Command
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 'Copy service'
14
- description 'Will create a new services, copy existing proxy settings, metrics, methods, application plans and mapping rules.'
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
- required :s, :source, '3scale source instance. Format: "http[s]://<provider_key>@3scale_url"'
17
- required :d, :destination, '3scale target instance. Format: "http[s]://<provider_key>@3scale_url"'
18
- required :t, 'target_system_name', 'Target system name'
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
- run do |opts, args, _|
21
- CopyServiceSubcommand.run opts, args
22
- end
22
+ runner CopyServiceSubcommand
23
23
  end
24
24
  end
25
25
 
26
- def self.run(opts, args)
27
- source = fetch_required_option(opts, :source)
28
- destination = fetch_required_option(opts, :destination)
29
- system_name = fetch_required_option(opts, :target_system_name)
30
- insecure = opts[:insecure] || false
31
- exit_with_message 'error: missing service_id argument' if args.empty?
32
- service_id = args[0]
33
- copy_service(service_id, source, destination, system_name, insecure)
34
- end
35
-
36
- def self.exit_with_message(message)
37
- puts message
38
- exit 1
39
- end
40
-
41
- def self.fetch_required_option(options, key)
42
- options.fetch(key) { exit_with_message "error: Missing argument #{key}" }
43
- end
44
-
45
- def self.compare_hashes(first, second, keys)
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
- # Returns new hash object with not nil valid params
59
- def self.filter_params(valid_params, source)
60
- valid_params.each_with_object({}) do |key, target|
61
- target[key] = source[key] unless source[key].nil?
62
- end
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 self.copy_service(service_id, source, destination, system_name, insecure)
73
- require '3scale/api'
74
-
75
- source_client = ThreeScale::API.new(
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
@@ -4,7 +4,7 @@ require '3scale_toolbox/base_command'
4
4
  module ThreeScaleToolbox
5
5
  module Commands
6
6
  module HelpCommand
7
- extend ThreeScaleToolbox::Command
7
+ include ThreeScaleToolbox::Command
8
8
  def self.command
9
9
  Cri::Command.new_basic_help
10
10
  end