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
@@ -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
|
-
|
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 '
|
14
|
-
description '3scale
|
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
|
-
|
11
|
-
|
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 '
|
17
|
+
summary 'import csv file'
|
17
18
|
description 'Create new services, metrics, methods and mapping rules from CSV formatted file'
|
18
19
|
|
19
|
-
|
20
|
-
|
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
|
-
|
23
|
-
ImportCsvSubcommand.run opts, args
|
24
|
-
end
|
23
|
+
runner ImportCsvSubcommand
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
28
|
-
def
|
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
|
60
|
-
|
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
|
167
|
-
destination = fetch_required_option(
|
168
|
-
file_path = fetch_required_option(
|
169
|
-
|
170
|
-
import_csv(destination, file_path
|
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
|