3scale_toolbox 0.7.0 → 0.8.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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1
  3. data/lib/3scale_toolbox.rb +2 -0
  4. data/lib/3scale_toolbox/3scale_client_factory.rb +9 -5
  5. data/lib/3scale_toolbox/base_command.rb +5 -1
  6. data/lib/3scale_toolbox/cli.rb +8 -4
  7. data/lib/3scale_toolbox/commands/3scale_command.rb +6 -0
  8. data/lib/3scale_toolbox/commands/copy_command.rb +4 -0
  9. data/lib/3scale_toolbox/commands/import_command.rb +4 -0
  10. data/lib/3scale_toolbox/commands/import_command/openapi.rb +23 -11
  11. data/lib/3scale_toolbox/commands/import_command/openapi/create_activedocs_step.rb +4 -2
  12. data/lib/3scale_toolbox/commands/import_command/openapi/create_service_step.rb +6 -1
  13. data/lib/3scale_toolbox/commands/import_command/openapi/method.rb +1 -1
  14. data/lib/3scale_toolbox/commands/import_command/openapi/resource_reader.rb +2 -0
  15. data/lib/3scale_toolbox/commands/import_command/openapi/step.rb +12 -0
  16. data/lib/3scale_toolbox/commands/import_command/openapi/threescale_api_spec.rb +36 -1
  17. data/lib/3scale_toolbox/commands/import_command/openapi/update_policies_step.rb +89 -0
  18. data/lib/3scale_toolbox/commands/import_command/openapi/update_service_oidc_conf_step.rb +59 -0
  19. data/lib/3scale_toolbox/commands/import_command/openapi/update_service_proxy_step.rb +68 -0
  20. data/lib/3scale_toolbox/commands/update_command.rb +4 -0
  21. data/lib/3scale_toolbox/entities/service.rb +29 -4
  22. data/lib/3scale_toolbox/helper.rb +6 -0
  23. data/lib/3scale_toolbox/proxy_logger.rb +20 -0
  24. data/lib/3scale_toolbox/swagger.rb +1 -0
  25. data/lib/3scale_toolbox/swagger/swagger.rb +121 -0
  26. data/lib/3scale_toolbox/tasks/update_service_settings_task.rb +19 -2
  27. data/lib/3scale_toolbox/version.rb +1 -1
  28. data/resources/swagger_meta_schema.json +1607 -0
  29. metadata +15 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f412bd1eba1df7d510acff344fc72fca45f7aa94c9317e10fe2425b611276f5
4
- data.tar.gz: a2bd5e4bfc83e1be12abdecbb636a18a43de81ed86594d590109e8734a59f6db
3
+ metadata.gz: 15a0f5fc1496d1414d2e5a940b1d08d8834dfc23a226f7c3a6d17d3014f1042f
4
+ data.tar.gz: e09c49ae0c9023757c2cf22dd2c10a61d009337a67aca4d8080ae2fb7a65eb2d
5
5
  SHA512:
6
- metadata.gz: 8a72ea781cf845c49aeb2a6b45f3576bcd76d534b086f377afcedcab0ed956bdb74cfb01a24b7697f39b6fbeaad12290b362b75796feb9cf259ac4d14cc9fee9
7
- data.tar.gz: 55c946b9199edaea9dcb0583d8b06ac5769641ab25ba646ca79c3cbb138f744177c4d2d75239d2daa482aaadf735f1906f71056c56bef91916bcade642529109
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
@@ -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
@@ -15,7 +15,9 @@ module ThreeScaleToolbox::CLI
15
15
 
16
16
  def self.install_signal_handlers
17
17
  # Set exit handler
18
- %w[INT TERM].each do |signal|
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.trap('USR1') do
28
- puts 'Caught USR1; dumping a stack trace'
29
- puts caller.map { |i| " #{i}" }.join("\n")
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)
@@ -13,6 +13,10 @@ module ThreeScaleToolbox
13
13
  usage 'import <sub-command> [options]'
14
14
  summary 'import super command'
15
15
  description 'Importing 3scale entities'
16
+
17
+ run do |_opts, _args, cmd|
18
+ puts cmd.help
19
+ end
16
20
  end
17
21
  end
18
22
  add_subcommand(ImportCsvSubcommand)
@@ -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 create_context(openapi_resource)
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
- # Disable validation step because https://petstore.swagger.io/v2/swagger.json
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.to_json,
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.tr(' ', '_')
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
@@ -16,7 +16,7 @@ module ThreeScaleToolbox
16
16
  end
17
17
 
18
18
  def system_name
19
- friendly_name.downcase
19
+ friendly_name.downcase.gsub(/[^\w]/, '_')
20
20
  end
21
21
 
22
22
  def operation_id
@@ -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.operationId
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