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
@@ -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