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
@@ -1,20 +1,22 @@
1
1
  require 'cri'
2
2
  require '3scale_toolbox/base_command'
3
3
  require '3scale_toolbox/commands/import_command/import_csv'
4
+ require '3scale_toolbox/commands/import_command/openapi'
4
5
 
5
6
  module ThreeScaleToolbox
6
7
  module Commands
7
8
  module ImportCommand
8
- extend ThreeScaleToolbox::Command
9
+ include ThreeScaleToolbox::Command
9
10
  def self.command
10
11
  Cri::Command.define do
11
12
  name 'import'
12
- usage 'import <command> [options]'
13
- summary '3scale import command'
14
- description '3scale import command.'
13
+ usage 'import <sub-command> [options]'
14
+ summary 'import super command'
15
+ description 'Importing 3scale entities'
15
16
  end
16
17
  end
17
18
  add_subcommand(ImportCsvSubcommand)
19
+ add_subcommand(OpenAPI::OpenAPISubcommand)
18
20
  end
19
21
  end
20
22
  end
@@ -7,45 +7,24 @@ require '3scale_toolbox/base_command'
7
7
  module ThreeScaleToolbox
8
8
  module Commands
9
9
  module ImportCommand
10
- module ImportCsvSubcommand
11
- extend ThreeScaleToolbox::Command
10
+ class ImportCsvSubcommand < Cri::CommandRunner
11
+ include ThreeScaleToolbox::Command
12
+
12
13
  def self.command
13
14
  Cri::Command.define do
14
15
  name 'csv'
15
16
  usage 'csv [opts] -d <dst> -f <file>'
16
- summary 'Import csv file'
17
+ summary 'import csv file'
17
18
  description 'Create new services, metrics, methods and mapping rules from CSV formatted file'
18
19
 
19
- required :d, :destination, '3scale target instance. Format: "http[s]://<provider_key>@3scale_url"'
20
- required :f, 'file', 'CSV formatted file'
20
+ option :d, :destination, '3scale target instance. Url or remote name', argument: :required
21
+ option :f, 'file', 'CSV formatted file', argument: :required
21
22
 
22
- run do |opts, args, _|
23
- ImportCsvSubcommand.run opts, args
24
- end
23
+ runner ImportCsvSubcommand
25
24
  end
26
25
  end
27
26
 
28
- def self.exit_with_message(message)
29
- puts message
30
- exit 1
31
- end
32
-
33
- def self.fetch_required_option(options, key)
34
- options.fetch(key) { exit_with_message "error: Missing argument #{key}" }
35
- end
36
-
37
- def self.provider_key_from_url(url)
38
- URI(url).user
39
- end
40
-
41
- def self.endpoint_from_url(url)
42
- uri = URI(url)
43
- uri.user = nil
44
-
45
- uri.to_s
46
- end
47
-
48
- def self.auth_app_key_according_service(service)
27
+ def auth_app_key_according_service(service)
49
28
  case service['backend_version']
50
29
  when '1'
51
30
  'user_key'
@@ -56,14 +35,9 @@ module ThreeScaleToolbox
56
35
  end
57
36
  end
58
37
 
59
- def self.import_csv(destination, file_path, insecure)
60
- endpoint = endpoint_from_url destination
61
- provider_key = provider_key_from_url destination
38
+ def import_csv(destination, file_path)
39
+ client = threescale_client(destination)
62
40
 
63
- client = ThreeScale::API.new(endpoint: endpoint,
64
- provider_key: provider_key,
65
- verify_ssl: !insecure
66
- )
67
41
  data = CSV.read file_path
68
42
  headings = data.shift
69
43
  services = {}
@@ -163,11 +137,11 @@ module ThreeScaleToolbox
163
137
  puts "#{stats[:mapping_rules]} mapping rules have been created"
164
138
  end
165
139
 
166
- def self.run(opts, _)
167
- destination = fetch_required_option(opts, :destination)
168
- file_path = fetch_required_option(opts, :file)
169
- insecure = opts[:insecure] || false
170
- import_csv(destination, file_path, insecure)
140
+ def run
141
+ destination = fetch_required_option(:destination)
142
+ file_path = fetch_required_option(:file)
143
+
144
+ import_csv(destination, file_path)
171
145
  end
172
146
  end
173
147
  end
@@ -0,0 +1,70 @@
1
+ require 'swagger'
2
+ require '3scale_toolbox/commands/import_command/openapi/method'
3
+ require '3scale_toolbox/commands/import_command/openapi/mapping_rule'
4
+ require '3scale_toolbox/commands/import_command/openapi/operation'
5
+ require '3scale_toolbox/commands/import_command/openapi/step'
6
+ require '3scale_toolbox/commands/import_command/openapi/resource_reader'
7
+ require '3scale_toolbox/commands/import_command/openapi/threescale_api_spec'
8
+ require '3scale_toolbox/commands/import_command/openapi/create_method_step'
9
+ require '3scale_toolbox/commands/import_command/openapi/create_mapping_rule_step'
10
+ require '3scale_toolbox/commands/import_command/openapi/create_service_step'
11
+
12
+ module ThreeScaleToolbox
13
+ module Commands
14
+ module ImportCommand
15
+ module OpenAPI
16
+ class OpenAPISubcommand < Cri::CommandRunner
17
+ include ThreeScaleToolbox::Command
18
+ include ResourceReader
19
+
20
+ def self.command
21
+ Cri::Command.define do
22
+ name 'openapi'
23
+ usage 'openapi [opts] -d <dst> <spec>'
24
+ summary 'Import API defintion in OpenAPI specification'
25
+ description 'Using an API definition format like OpenAPI, import to your 3scale API'
26
+
27
+ option :d, :destination, '3scale target instance. Format: "http[s]://<authentication>@3scale_domain"', argument: :required
28
+ option :t, 'target_system_name', 'Target system name', argument: :required
29
+ param :openapi_resource
30
+
31
+ runner OpenAPISubcommand
32
+ end
33
+ end
34
+
35
+ def run
36
+ context = create_context
37
+
38
+ tasks = []
39
+ tasks << CreateServiceStep.new(context)
40
+ tasks << CreateMethodsStep.new(context)
41
+ tasks << ThreeScaleToolbox::Tasks::DestroyMappingRulesTask.new(context)
42
+ tasks << CreateMappingRulesStep.new(context)
43
+
44
+ # run tasks
45
+ tasks.each(&:call)
46
+ end
47
+
48
+ private
49
+
50
+ def create_context
51
+ {
52
+ api_spec: ThreeScaleApiSpec.new(load_openapi),
53
+ threescale_client: threescale_client(fetch_required_option(:destination)),
54
+ target_system_name: options[:target_system_name]
55
+ }
56
+ end
57
+
58
+ def load_openapi
59
+ Swagger.build(load_resource(arguments[:openapi_resource]))
60
+ # Disable validation step because https://petstore.swagger.io/v2/swagger.json
61
+ # does not pass validation. Maybe library's schema is outdated?
62
+ # openapi.tap(&:validate)
63
+ rescue Swagger::InvalidDefinition, Hashie::CoercionError, Psych::SyntaxError => e
64
+ raise ThreeScaleToolbox::Error, "OpenAPI schema validation failed: #{e.message}"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,18 @@
1
+ module ThreeScaleToolbox
2
+ module Commands
3
+ module ImportCommand
4
+ module OpenAPI
5
+ class CreateMappingRulesStep
6
+ include Step
7
+
8
+ def call
9
+ operations.each do |op|
10
+ service.create_mapping_rule(op.mapping_rule)
11
+ puts "Created #{op.http_method} #{op.pattern} endpoint"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ module ThreeScaleToolbox
2
+ module Commands
3
+ module ImportCommand
4
+ module OpenAPI
5
+ class CreateMethodsStep
6
+ include Step
7
+
8
+ def call
9
+ hits_metric_id = service.hits['id']
10
+ operations.each do |op|
11
+ res = service.create_method(hits_metric_id, op.method)
12
+ metric_id = res['id']
13
+ # if method system_name exists, ignore error and get metric_id
14
+ # Make operation indempotent
15
+ unless res['errors'].nil?
16
+ if !res['errors']['system_name'].nil? \
17
+ && res['errors']['system_name'][0] =~ /has already been taken/
18
+ metric_id = method_id_by_system_name[op.method['system_name']]
19
+ else
20
+ raise Error, "Metohd has not been saved. Errors: #{res['errors']}"
21
+ end
22
+ end
23
+
24
+ op.set(:metric_id, metric_id)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def method_id_by_system_name
31
+ @method_id_by_system_name ||= service.methods.each_with_object({}) do |method, acc|
32
+ acc[method['system_name']] = method['id']
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,69 @@
1
+ module ThreeScaleToolbox
2
+ module Commands
3
+ module ImportCommand
4
+ module OpenAPI
5
+ class CreateServiceStep
6
+ include Step
7
+
8
+ ##
9
+ # Creates service with a given system_name
10
+ # If service already exists, update basic settings like name and description
11
+ def call
12
+ # Create service and update context
13
+ self.service = Entities::Service.create(remote: threescale_client,
14
+ service: service_settings,
15
+ system_name: service_system_name)
16
+ puts "Created service id: #{service.id}, name: #{service_name}"
17
+ rescue ThreeScaleToolbox::Error => e
18
+ raise unless e.message =~ /"system_name"=>\["has already been taken"\]/
19
+
20
+ # Update service and update context
21
+ self.service = Entities::Service.new(id: service_id, remote: threescale_client)
22
+ service.update_service(service_settings)
23
+ puts "Updated service id: #{service.id}, name: #{service_name}"
24
+ end
25
+
26
+ private
27
+
28
+ def service_system_name
29
+ target_system_name || service_name.downcase.tr(' ', '_')
30
+ end
31
+
32
+ def service_id
33
+ @service_id ||= fetch_service_id
34
+ end
35
+
36
+ def fetch_service_id
37
+ # figure out service by system_name
38
+ service_found = threescale_client.list_services.find do |svc|
39
+ svc['system_name'] == service_system_name
40
+ end
41
+ # It should exist
42
+ raise ThreeScaleToolbox::Error, "Service with system_name: #{service_system_name}, should exist" if service_found.nil?
43
+
44
+ service_found['id']
45
+ end
46
+
47
+ def service_settings
48
+ default_service_settings.tap do |svc|
49
+ svc['name'] = service_name
50
+ svc['description'] = service_description
51
+ end
52
+ end
53
+
54
+ def default_service_settings
55
+ {}
56
+ end
57
+
58
+ def service_name
59
+ api_spec.title
60
+ end
61
+
62
+ def service_description
63
+ api_spec.description
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,35 @@
1
+ module ThreeScaleToolbox
2
+ module Commands
3
+ module ImportCommand
4
+ module OpenAPI
5
+ module MappingRule
6
+ def mapping_rule
7
+ {
8
+ 'pattern' => pattern,
9
+ 'http_method' => http_method,
10
+ 'delta' => delta,
11
+ 'metric_id' => metric_id
12
+ }
13
+ end
14
+
15
+ def http_method
16
+ operation[:verb].upcase
17
+ end
18
+
19
+ def pattern
20
+ # apply strict matching
21
+ operation[:path] + '$'
22
+ end
23
+
24
+ def delta
25
+ 1
26
+ end
27
+
28
+ def metric_id
29
+ operation[:metric_id]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ module ThreeScaleToolbox
2
+ module Commands
3
+ module ImportCommand
4
+ module OpenAPI
5
+ module Method
6
+
7
+ def method
8
+ {
9
+ 'friendly_name' => friendly_name,
10
+ 'system_name' => system_name
11
+ }
12
+ end
13
+
14
+ def friendly_name
15
+ operation[:operationId]
16
+ end
17
+
18
+ def system_name
19
+ friendly_name.downcase
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ module ThreeScaleToolbox
2
+ module Commands
3
+ module ImportCommand
4
+ module OpenAPI
5
+ class Operation
6
+ include Method
7
+ include MappingRule
8
+
9
+ attr_reader :operation
10
+
11
+ def initialize(operation)
12
+ @operation = operation
13
+ end
14
+
15
+ def set(key, val)
16
+ operation[key] = val
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,49 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+
4
+ module ThreeScaleToolbox
5
+ module Commands
6
+ module ImportCommand
7
+ module OpenAPI
8
+ module ResourceReader
9
+ ##
10
+ # Load resource from different types of sources.
11
+ # Supported types are: file, URL, stdin
12
+ # Loaded content is returned
13
+ def load_resource(resource)
14
+ # Json format is parsed as well
15
+ YAML.safe_load(read_content(resource))
16
+ end
17
+
18
+ ##
19
+ # Reads resources from different types of sources.
20
+ # Supported types are: file, URL, stdin
21
+ # Resource raw content is returned
22
+ def read_content(resource)
23
+ case resource
24
+ when '-'
25
+ method(:read_stdin)
26
+ when /\A#{URI::DEFAULT_PARSER.make_regexp}\z/
27
+ method(:read_url)
28
+ else
29
+ method(:read_file)
30
+ end.call(resource)
31
+ end
32
+
33
+ # Detect format from file extension
34
+ def read_file(resource)
35
+ File.read(resource)
36
+ end
37
+
38
+ def read_stdin(_resource)
39
+ STDIN.read
40
+ end
41
+
42
+ def read_url(resource)
43
+ Net::HTTP.get(URI.parse(resource))
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end