3scale_toolbox 0.7.0 → 0.8.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 +7 -1
- data/lib/3scale_toolbox.rb +2 -0
- data/lib/3scale_toolbox/3scale_client_factory.rb +9 -5
- data/lib/3scale_toolbox/base_command.rb +5 -1
- data/lib/3scale_toolbox/cli.rb +8 -4
- data/lib/3scale_toolbox/commands/3scale_command.rb +6 -0
- data/lib/3scale_toolbox/commands/copy_command.rb +4 -0
- data/lib/3scale_toolbox/commands/import_command.rb +4 -0
- data/lib/3scale_toolbox/commands/import_command/openapi.rb +23 -11
- data/lib/3scale_toolbox/commands/import_command/openapi/create_activedocs_step.rb +4 -2
- data/lib/3scale_toolbox/commands/import_command/openapi/create_service_step.rb +6 -1
- data/lib/3scale_toolbox/commands/import_command/openapi/method.rb +1 -1
- data/lib/3scale_toolbox/commands/import_command/openapi/resource_reader.rb +2 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/step.rb +12 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/threescale_api_spec.rb +36 -1
- data/lib/3scale_toolbox/commands/import_command/openapi/update_policies_step.rb +89 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/update_service_oidc_conf_step.rb +59 -0
- data/lib/3scale_toolbox/commands/import_command/openapi/update_service_proxy_step.rb +68 -0
- data/lib/3scale_toolbox/commands/update_command.rb +4 -0
- data/lib/3scale_toolbox/entities/service.rb +29 -4
- data/lib/3scale_toolbox/helper.rb +6 -0
- data/lib/3scale_toolbox/proxy_logger.rb +20 -0
- data/lib/3scale_toolbox/swagger.rb +1 -0
- data/lib/3scale_toolbox/swagger/swagger.rb +121 -0
- data/lib/3scale_toolbox/tasks/update_service_settings_task.rb +19 -2
- data/lib/3scale_toolbox/version.rb +1 -1
- data/resources/swagger_meta_schema.json +1607 -0
- metadata +15 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15a0f5fc1496d1414d2e5a940b1d08d8834dfc23a226f7c3a6d17d3014f1042f
|
4
|
+
data.tar.gz: e09c49ae0c9023757c2cf22dd2c10a61d009337a67aca4d8080ae2fb7a65eb2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd16657b1d83f114fdaf3d4b0ad1fd39fc08ab3a267e441b332333d22933e620cb7bd1cad48de66711594a9c10fcee8bed894e0b03170baaf6cfee53d7decfd5
|
7
|
+
data.tar.gz: 16838e3a0c469ed68f4064babce60fe207d77a06c8f869035115422a1d461360da3f2237fd45c6f31d0cd62602646254f4ed2b6a5b25546e318a2afa85e2732a
|
data/README.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# 3scale toolbox
|
2
|
+
[![Build Status](https://travis-ci.org/3scale/3scale_toolbox.svg?branch=master)](https://travis-ci.org/3scale/3scale_toolbox)
|
2
3
|
|
4
|
+
This software is licensed under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0).
|
5
|
+
|
6
|
+
See the LICENSE and NOTICE files that should have been provided along with this software for details.
|
7
|
+
|
8
|
+
## Description
|
3
9
|
3scale toolbox is a set of tools to help you manage your 3scale product. Using the [3scale API Ruby Client](https://github.com/3scale/3scale-api-ruby).
|
4
10
|
|
5
11
|
## Table of contents
|
@@ -76,7 +82,7 @@ OPTIONS
|
|
76
82
|
remote name
|
77
83
|
-s --source=<value> 3scale source instance. Url or
|
78
84
|
remote name
|
79
|
-
-t --target_system_name=<value> Target system name. Default to
|
85
|
+
-t --target_system_name=<value> Target system name. Default to
|
80
86
|
source system name
|
81
87
|
|
82
88
|
OPTIONS FOR COPY
|
data/lib/3scale_toolbox.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require '3scale_toolbox/version'
|
2
2
|
require '3scale_toolbox/helper'
|
3
3
|
require '3scale_toolbox/error'
|
4
|
+
require '3scale_toolbox/proxy_logger'
|
5
|
+
require '3scale_toolbox/swagger'
|
4
6
|
require '3scale_toolbox/configuration'
|
5
7
|
require '3scale_toolbox/remotes'
|
6
8
|
require '3scale_toolbox/3scale_client_factory'
|
@@ -1,17 +1,18 @@
|
|
1
1
|
module ThreeScaleToolbox
|
2
2
|
class ThreeScaleClientFactory
|
3
3
|
class << self
|
4
|
-
def get(remotes, remote_str, verify_ssl)
|
5
|
-
new(remotes, remote_str, verify_ssl).call
|
4
|
+
def get(remotes, remote_str, verify_ssl, verbose = false)
|
5
|
+
new(remotes, remote_str, verify_ssl, verbose).call
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
attr_reader :remotes, :remote_str, :verify_ssl
|
9
|
+
attr_reader :remotes, :remote_str, :verify_ssl, :verbose
|
10
10
|
|
11
|
-
def initialize(remotes, remote_str, verify_ssl)
|
11
|
+
def initialize(remotes, remote_str, verify_ssl, verbose)
|
12
12
|
@remotes = remotes
|
13
13
|
@remote_str = remote_str
|
14
14
|
@verify_ssl = verify_ssl
|
15
|
+
@verbose = verbose
|
15
16
|
end
|
16
17
|
|
17
18
|
def call
|
@@ -21,7 +22,10 @@ module ThreeScaleToolbox
|
|
21
22
|
remote = remotes.fetch(remote_str)
|
22
23
|
end
|
23
24
|
|
24
|
-
remote_client(remote.merge(verify_ssl: verify_ssl))
|
25
|
+
client = remote_client(remote.merge(verify_ssl: verify_ssl))
|
26
|
+
return ProxyLogger.new(client) if verbose
|
27
|
+
|
28
|
+
client
|
25
29
|
end
|
26
30
|
|
27
31
|
private
|
@@ -47,7 +47,7 @@ module ThreeScaleToolbox
|
|
47
47
|
# Input param can be endpoint url or remote name
|
48
48
|
#
|
49
49
|
def threescale_client(str)
|
50
|
-
ThreeScaleClientFactory.get(remotes, str, verify_ssl)
|
50
|
+
ThreeScaleClientFactory.get(remotes, str, verify_ssl, verbose)
|
51
51
|
end
|
52
52
|
|
53
53
|
def verify_ssl
|
@@ -55,6 +55,10 @@ module ThreeScaleToolbox
|
|
55
55
|
!options[:insecure]
|
56
56
|
end
|
57
57
|
|
58
|
+
def verbose
|
59
|
+
options[:verbose]
|
60
|
+
end
|
61
|
+
|
58
62
|
def exit_with_message(message)
|
59
63
|
raise ThreeScaleToolbox::Error, message
|
60
64
|
end
|
data/lib/3scale_toolbox/cli.rb
CHANGED
@@ -15,7 +15,9 @@ module ThreeScaleToolbox::CLI
|
|
15
15
|
|
16
16
|
def self.install_signal_handlers
|
17
17
|
# Set exit handler
|
18
|
-
|
18
|
+
# Only OS supported signals
|
19
|
+
available_signals = %w[INT TERM].select { |signal_name| Signal.list.key? signal_name }
|
20
|
+
available_signals.each do |signal|
|
19
21
|
Signal.trap(signal) do
|
20
22
|
puts
|
21
23
|
exit!(0)
|
@@ -24,9 +26,11 @@ module ThreeScaleToolbox::CLI
|
|
24
26
|
|
25
27
|
# Set stack trace dump handler
|
26
28
|
if !defined?(RUBY_ENGINE) || RUBY_ENGINE != 'jruby'
|
27
|
-
Signal.
|
28
|
-
|
29
|
-
|
29
|
+
if Signal.list.key? 'USR1'
|
30
|
+
Signal.trap('USR1') do
|
31
|
+
puts 'Caught USR1; dumping a stack trace'
|
32
|
+
puts caller.map { |i| " #{i}" }.join("\n")
|
33
|
+
end
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
@@ -6,6 +6,7 @@ module ThreeScaleToolbox
|
|
6
6
|
module Commands
|
7
7
|
module ThreeScaleCommand
|
8
8
|
include ThreeScaleToolbox::Command
|
9
|
+
|
9
10
|
def self.command
|
10
11
|
Cri::Command.define do
|
11
12
|
name '3scale'
|
@@ -19,10 +20,15 @@ module ThreeScaleToolbox
|
|
19
20
|
exit 0
|
20
21
|
end
|
21
22
|
flag :k, :insecure, 'Proceed and operate even for server connections otherwise considered insecure'
|
23
|
+
flag nil, :verbose, 'Verbose mode'
|
22
24
|
flag :h, :help, 'show help for this command' do |_, cmd|
|
23
25
|
puts cmd.help
|
24
26
|
exit 0
|
25
27
|
end
|
28
|
+
|
29
|
+
run do |_opts, _args, cmd|
|
30
|
+
puts cmd.help
|
31
|
+
end
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
@@ -12,6 +12,10 @@ module ThreeScaleToolbox
|
|
12
12
|
usage 'copy <sub-command> [options]'
|
13
13
|
summary 'copy super command'
|
14
14
|
description 'Copy 3scale entities between tenants'
|
15
|
+
|
16
|
+
run do |_opts, _args, cmd|
|
17
|
+
puts cmd.help
|
18
|
+
end
|
15
19
|
end
|
16
20
|
end
|
17
21
|
add_subcommand(CopyServiceSubcommand)
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'swagger'
|
2
1
|
require '3scale_toolbox/commands/import_command/openapi/method'
|
3
2
|
require '3scale_toolbox/commands/import_command/openapi/mapping_rule'
|
4
3
|
require '3scale_toolbox/commands/import_command/openapi/operation'
|
@@ -9,6 +8,9 @@ require '3scale_toolbox/commands/import_command/openapi/create_method_step'
|
|
9
8
|
require '3scale_toolbox/commands/import_command/openapi/create_mapping_rule_step'
|
10
9
|
require '3scale_toolbox/commands/import_command/openapi/create_service_step'
|
11
10
|
require '3scale_toolbox/commands/import_command/openapi/create_activedocs_step'
|
11
|
+
require '3scale_toolbox/commands/import_command/openapi/update_service_proxy_step'
|
12
|
+
require '3scale_toolbox/commands/import_command/openapi/update_service_oidc_conf_step'
|
13
|
+
require '3scale_toolbox/commands/import_command/openapi/update_policies_step'
|
12
14
|
|
13
15
|
module ThreeScaleToolbox
|
14
16
|
module Commands
|
@@ -27,6 +29,10 @@ module ThreeScaleToolbox
|
|
27
29
|
|
28
30
|
option :d, :destination, '3scale target instance. Format: "http[s]://<authentication>@3scale_domain"', argument: :required
|
29
31
|
option :t, 'target_system_name', 'Target system name', argument: :required
|
32
|
+
flag nil, 'activedocs-hidden', 'Create ActiveDocs in hidden state'
|
33
|
+
flag nil, 'skip-openapi-validation', 'Skip OpenAPI schema validation'
|
34
|
+
option nil, 'oidc-issuer-endpoint', 'OIDC Issuer Endpoint', argument: :required
|
35
|
+
option nil, 'default-credentials-userkey', 'Default credentials policy userkey', argument: :required
|
30
36
|
param :openapi_resource
|
31
37
|
|
32
38
|
runner OpenAPISubcommand
|
@@ -34,15 +40,15 @@ module ThreeScaleToolbox
|
|
34
40
|
end
|
35
41
|
|
36
42
|
def run
|
37
|
-
openapi_resource = load_resource(arguments[:openapi_resource])
|
38
|
-
context = create_context(openapi_resource)
|
39
|
-
|
40
43
|
tasks = []
|
41
44
|
tasks << CreateServiceStep.new(context)
|
42
45
|
tasks << CreateMethodsStep.new(context)
|
43
46
|
tasks << ThreeScaleToolbox::Tasks::DestroyMappingRulesTask.new(context)
|
44
47
|
tasks << CreateMappingRulesStep.new(context)
|
45
48
|
tasks << CreateActiveDocsStep.new(context)
|
49
|
+
tasks << UpdateServiceProxyStep.new(context)
|
50
|
+
tasks << UpdateServiceOidcConfStep.new(context)
|
51
|
+
tasks << UpdatePoliciesStep.new(context)
|
46
52
|
|
47
53
|
# run tasks
|
48
54
|
tasks.each(&:call)
|
@@ -50,21 +56,27 @@ module ThreeScaleToolbox
|
|
50
56
|
|
51
57
|
private
|
52
58
|
|
53
|
-
def
|
59
|
+
def context
|
60
|
+
@context ||= create_context
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_context
|
64
|
+
openapi_resource = load_resource(arguments[:openapi_resource])
|
54
65
|
{
|
55
66
|
api_spec_resource: openapi_resource,
|
56
67
|
api_spec: ThreeScaleApiSpec.new(load_openapi(openapi_resource)),
|
57
68
|
threescale_client: threescale_client(fetch_required_option(:destination)),
|
58
|
-
target_system_name: options[:target_system_name]
|
69
|
+
target_system_name: options[:target_system_name],
|
70
|
+
activedocs_published: !options[:'activedocs-hidden'],
|
71
|
+
oidc_issuer_endpoint: options[:'oidc-issuer-endpoint'],
|
72
|
+
default_credentials_userkey: options[:'default-credentials-userkey'],
|
73
|
+
skip_openapi_validation: options[:'skip-openapi-validation']
|
59
74
|
}
|
60
75
|
end
|
61
76
|
|
62
77
|
def load_openapi(openapi_resource)
|
63
|
-
Swagger.build(openapi_resource)
|
64
|
-
|
65
|
-
# does not pass validation. Maybe library's schema is outdated?
|
66
|
-
# openapi.tap(&:validate)
|
67
|
-
rescue Swagger::InvalidDefinition, Hashie::CoercionError, Psych::SyntaxError => e
|
78
|
+
Swagger.build(openapi_resource, validate: !options[:'skip-openapi-validation'])
|
79
|
+
rescue JSON::Schema::ValidationError => e
|
68
80
|
raise ThreeScaleToolbox::Error, "OpenAPI schema validation failed: #{e.message}"
|
69
81
|
end
|
70
82
|
end
|
@@ -10,8 +10,10 @@ module ThreeScaleToolbox
|
|
10
10
|
name: api_spec.title,
|
11
11
|
system_name: activedocs_system_name,
|
12
12
|
service_id: service.id,
|
13
|
-
body: resource
|
14
|
-
description: api_spec.description
|
13
|
+
body: JSON.pretty_generate(resource),
|
14
|
+
description: api_spec.description,
|
15
|
+
published: context[:activedocs_published],
|
16
|
+
skip_swagger_validations: context[:skip_openapi_validation]
|
15
17
|
}
|
16
18
|
|
17
19
|
res = threescale_client.create_activedocs(active_doc)
|
@@ -26,7 +26,7 @@ module ThreeScaleToolbox
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def service_system_name
|
29
|
-
target_system_name || service_name.downcase.
|
29
|
+
target_system_name || service_name.downcase.gsub(/[^\w]/, '_')
|
30
30
|
end
|
31
31
|
|
32
32
|
def service_id
|
@@ -48,6 +48,7 @@ module ThreeScaleToolbox
|
|
48
48
|
default_service_settings.tap do |svc|
|
49
49
|
svc['name'] = service_name
|
50
50
|
svc['description'] = service_description
|
51
|
+
svc['backend_version'] = backend_version
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
@@ -62,6 +63,10 @@ module ThreeScaleToolbox
|
|
62
63
|
def service_description
|
63
64
|
api_spec.description
|
64
65
|
end
|
66
|
+
|
67
|
+
def backend_version
|
68
|
+
api_spec.backend_version
|
69
|
+
end
|
65
70
|
end
|
66
71
|
end
|
67
72
|
end
|
@@ -13,6 +13,8 @@ module ThreeScaleToolbox
|
|
13
13
|
def load_resource(resource)
|
14
14
|
# Json format is parsed as well
|
15
15
|
YAML.safe_load(read_content(resource))
|
16
|
+
rescue Psych::SyntaxError => e
|
17
|
+
raise ThreeScaleToolbox::Error, "JSON/YAML validation failed: #{e.message}"
|
16
18
|
end
|
17
19
|
|
18
20
|
##
|
@@ -46,6 +46,18 @@ module ThreeScaleToolbox
|
|
46
46
|
def system_name_already_taken_error?(error)
|
47
47
|
Array(Hash(error)['system_name']).any? { |msg| msg.match(/has already been taken/) }
|
48
48
|
end
|
49
|
+
|
50
|
+
def security
|
51
|
+
api_spec.security
|
52
|
+
end
|
53
|
+
|
54
|
+
def oidc_issuer_endpoint
|
55
|
+
context[:oidc_issuer_endpoint]
|
56
|
+
end
|
57
|
+
|
58
|
+
def default_credentials_userkey
|
59
|
+
context[:default_credentials_userkey]
|
60
|
+
end
|
49
61
|
end
|
50
62
|
end
|
51
63
|
end
|
@@ -17,15 +17,50 @@ module ThreeScaleToolbox
|
|
17
17
|
openapi.info.description
|
18
18
|
end
|
19
19
|
|
20
|
+
def host
|
21
|
+
openapi.host
|
22
|
+
end
|
23
|
+
|
24
|
+
def schemes
|
25
|
+
Array(openapi.schemes)
|
26
|
+
end
|
27
|
+
|
28
|
+
def backend_version
|
29
|
+
# default authentication mode if no security requirement
|
30
|
+
return '1' if security.nil?
|
31
|
+
|
32
|
+
case security.type
|
33
|
+
when 'oauth2'
|
34
|
+
'oidc'
|
35
|
+
when 'apiKey'
|
36
|
+
'1'
|
37
|
+
else
|
38
|
+
raise ThreeScaleToolbox::Error, "Unexpected security scheme type #{security.type}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def security
|
43
|
+
@security ||= parse_security
|
44
|
+
end
|
45
|
+
|
20
46
|
def operations
|
21
47
|
openapi.operations.map do |op|
|
22
48
|
Operation.new(
|
23
49
|
path: "#{openapi.base_path}#{op.path}",
|
24
50
|
verb: op.verb,
|
25
|
-
operationId: op.
|
51
|
+
operationId: op.operation_id
|
26
52
|
)
|
27
53
|
end
|
28
54
|
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def parse_security
|
59
|
+
raise ThreeScaleToolbox::Error, 'Invalid OAS: multiple security requirements' \
|
60
|
+
if openapi.global_security_requirements.size > 1
|
61
|
+
|
62
|
+
openapi.global_security_requirements.first
|
63
|
+
end
|
29
64
|
end
|
30
65
|
end
|
31
66
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module ThreeScaleToolbox
|
2
|
+
module Commands
|
3
|
+
module ImportCommand
|
4
|
+
module OpenAPI
|
5
|
+
class UpdatePoliciesStep
|
6
|
+
include Step
|
7
|
+
|
8
|
+
##
|
9
|
+
# Updates Proxy Policies config
|
10
|
+
def call
|
11
|
+
# to be idempotent, we must read first
|
12
|
+
source_policies_settings = service.policies
|
13
|
+
# shallow copy
|
14
|
+
# to detect policies_settings changes, should only be updated setting objects
|
15
|
+
# do not update in-place, otherwise changes will not be detected
|
16
|
+
policies_settings = source_policies_settings.dup
|
17
|
+
|
18
|
+
add_anonymous_access_policy(policies_settings)
|
19
|
+
add_rh_sso_keycloak_role_check_policy(policies_settings)
|
20
|
+
|
21
|
+
return if source_policies_settings == policies_settings
|
22
|
+
|
23
|
+
res = service.update_policies('policies_config' => policies_settings)
|
24
|
+
if res.is_a?(Hash) && (errors = res['errors'])
|
25
|
+
raise ThreeScaleToolbox::Error, "Service policies have not been updated. #{errors}"
|
26
|
+
end
|
27
|
+
|
28
|
+
puts 'Service policies updated'
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def add_anonymous_access_policy(policies)
|
34
|
+
# only on 'open api' security req
|
35
|
+
return unless security.nil?
|
36
|
+
|
37
|
+
return if policies.any? { |policy| policy['name'] == 'default_credentials' }
|
38
|
+
|
39
|
+
# Anonymous policy should be before apicast policy
|
40
|
+
# hence, adding as a first element
|
41
|
+
policies.insert(0, anonymous_policy)
|
42
|
+
end
|
43
|
+
|
44
|
+
def anonymous_policy
|
45
|
+
raise ThreeScaleToolbox::Error, 'User key must be provided by ' \
|
46
|
+
'--default-credentials-userkey optional param' \
|
47
|
+
if default_credentials_userkey.nil?
|
48
|
+
|
49
|
+
{
|
50
|
+
'name': 'default_credentials',
|
51
|
+
'version': 'builtin',
|
52
|
+
'configuration': {
|
53
|
+
'auth_type': 'user_key',
|
54
|
+
'user_key': default_credentials_userkey
|
55
|
+
},
|
56
|
+
'enabled': true
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_rh_sso_keycloak_role_check_policy(policies)
|
61
|
+
# only applies to oauth2 sec type
|
62
|
+
return if security.nil? || security.type != 'oauth2'
|
63
|
+
|
64
|
+
return if policies.any? { |policy| policy['name'] == 'keycloak_role_check' }
|
65
|
+
|
66
|
+
policies << keycloak_policy
|
67
|
+
end
|
68
|
+
|
69
|
+
def keycloak_policy
|
70
|
+
{
|
71
|
+
'name': 'keycloak_role_check',
|
72
|
+
'version': 'builtin',
|
73
|
+
'configuration': {
|
74
|
+
'type': 'whitelist',
|
75
|
+
'scopes': [
|
76
|
+
{
|
77
|
+
'realm_roles': [],
|
78
|
+
'client_roles': security.scopes.map { |scope| { 'name': scope } }
|
79
|
+
}
|
80
|
+
]
|
81
|
+
},
|
82
|
+
'enabled': true
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|