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