├── .circleci ├── config.yml └── setup-rubygems.sh ├── .dependency_decisions.yml ├── .github └── workflows │ ├── fast-forward-branch.yaml │ └── tagRelease.yml ├── .gitignore ├── .rspec ├── .travis.yml ├── .travis ├── install_linux_env.sh ├── run_linux_tests.sh └── setup_linux_env.sh ├── 3scale_toolbox.gemspec ├── CHANGELOG.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── NOTICE ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── docs ├── activedocs.md ├── app-plan.md ├── applications.md ├── contributing.md ├── copy-backend.md ├── copy-policy-registry.md ├── copy-product.md ├── copy-service.md ├── errors.md ├── export-import-app-plan.md ├── export-import-policy-chain.md ├── export-import-product.md ├── import-csv.md ├── method.md ├── metric.md ├── openapi.md ├── plugins.md ├── proxy-config.md ├── proxy.md ├── remotes.md ├── service.md └── ssl_errors.md ├── examples ├── Jenkinsfile ├── import.csv ├── petstore_simple.yaml └── petstore_simple_no_servers.yaml ├── exe └── 3scale ├── lib ├── 3scale_toolbox.rb └── 3scale_toolbox │ ├── 3scale_client_factory.rb │ ├── attribute_filters.rb │ ├── attribute_filters │ ├── attribute_filter.rb │ └── service_id_from_ref_filter.rb │ ├── base_command.rb │ ├── cli.rb │ ├── cli │ ├── custom_table_printer.rb │ ├── error_handler.rb │ ├── json_printer.rb │ ├── null_printer.rb │ ├── output_flag.rb │ └── yaml_printer.rb │ ├── commands.rb │ ├── commands │ ├── 3scale_command.rb │ ├── account_command.rb │ ├── account_command │ │ └── find_command.rb │ ├── activedocs_command.rb │ ├── activedocs_command │ │ ├── apply_command.rb │ │ ├── create_command.rb │ │ ├── delete_command.rb │ │ └── list_command.rb │ ├── application_command.rb │ ├── application_command │ │ ├── apply_command.rb │ │ ├── create_command.rb │ │ ├── delete_command.rb │ │ ├── list_command.rb │ │ └── show_command.rb │ ├── backend_command.rb │ ├── backend_command │ │ ├── copy_command.rb │ │ └── copy_command │ │ │ ├── copy_mapping_rules_task.rb │ │ │ ├── copy_methods_task.rb │ │ │ ├── copy_metrics_task.rb │ │ │ ├── create_or_update_target_backend_task.rb │ │ │ ├── delete_mapping_rules_task.rb │ │ │ └── task.rb │ ├── copy_command.rb │ ├── copy_command │ │ └── service_command.rb │ ├── help_command.rb │ ├── import_command.rb │ ├── import_command │ │ ├── import_csv.rb │ │ ├── issuer_type_transformer.rb │ │ ├── openapi.rb │ │ └── openapi │ │ │ ├── create_activedocs_step.rb │ │ │ ├── create_backend_mapping_rule_step.rb │ │ │ ├── create_backend_method_step.rb │ │ │ ├── create_backend_step.rb │ │ │ ├── create_mapping_rule_step.rb │ │ │ ├── create_method_step.rb │ │ │ ├── create_service_step.rb │ │ │ ├── import_backend_step.rb │ │ │ ├── import_product_step.rb │ │ │ ├── mapping_rule.rb │ │ │ ├── method.rb │ │ │ ├── operation.rb │ │ │ ├── step.rb │ │ │ ├── update_policies_step.rb │ │ │ ├── update_service_oidc_conf_step.rb │ │ │ └── update_service_proxy_step.rb │ ├── methods_command.rb │ ├── methods_command │ │ ├── apply_command.rb │ │ ├── create_command.rb │ │ ├── delete_command.rb │ │ └── list_command.rb │ ├── metrics_command.rb │ ├── metrics_command │ │ ├── apply_command.rb │ │ ├── create_command.rb │ │ ├── delete_command.rb │ │ └── list_command.rb │ ├── plans_command.rb │ ├── plans_command │ │ ├── apply_command.rb │ │ ├── create_command.rb │ │ ├── delete_command.rb │ │ ├── export_command.rb │ │ ├── import │ │ │ ├── create_or_update_app_plan_step.rb │ │ │ ├── import_backend_metrics_step.rb │ │ │ ├── import_plan_features_step.rb │ │ │ ├── import_plan_limits_step.rb │ │ │ ├── import_plan_metrics_step.rb │ │ │ ├── import_plan_pricing_rules_step.rb │ │ │ ├── step.rb │ │ │ └── validate_plan_step.rb │ │ ├── import_command.rb │ │ ├── list_command.rb │ │ └── show_command.rb │ ├── policies_command.rb │ ├── policies_command │ │ ├── export_command.rb │ │ └── import_command.rb │ ├── policy_registry_command.rb │ ├── policy_registry_command │ │ └── copy_command.rb │ ├── product_command.rb │ ├── product_command │ │ ├── copy_command.rb │ │ ├── copy_command │ │ │ ├── copy_backends_task.rb │ │ │ └── delete_target_backend_usages_task.rb │ │ ├── export_command.rb │ │ └── import_command.rb │ ├── proxy_command.rb │ ├── proxy_command │ │ ├── deploy_command.rb │ │ ├── show_command.rb │ │ └── update_command.rb │ ├── proxy_config_command.rb │ ├── proxy_config_command │ │ ├── deploy_command.rb │ │ ├── export_command.rb │ │ ├── helper.rb │ │ ├── list_command.rb │ │ ├── promote_command.rb │ │ └── show_command.rb │ ├── remote_command.rb │ ├── remote_command │ │ ├── remote_add.rb │ │ ├── remote_list.rb │ │ ├── remote_remove.rb │ │ └── remote_rename.rb │ ├── service_command.rb │ └── service_command │ │ ├── apply_command.rb │ │ ├── copy_command.rb │ │ ├── copy_command │ │ ├── bump_proxy_version_task.rb │ │ ├── copy_activedocs_task.rb │ │ ├── copy_app_plans_task.rb │ │ ├── copy_limits_task.rb │ │ ├── copy_mapping_rules_task.rb │ │ ├── copy_methods_task.rb │ │ ├── copy_metrics_task.rb │ │ ├── copy_policies_task.rb │ │ ├── copy_pricingrules_task.rb │ │ ├── copy_service_proxy_task.rb │ │ ├── create_or_update_service_task.rb │ │ ├── destroy_mapping_rules_task.rb │ │ └── task.rb │ │ ├── create_command.rb │ │ ├── delete_command.rb │ │ ├── list_command.rb │ │ └── show_command.rb │ ├── configuration.rb │ ├── crds.rb │ ├── crds │ ├── application_plan_dump.rb │ ├── backend_dump.rb │ ├── backend_mapping_rule_dump.rb │ ├── backend_method_dump.rb │ ├── backend_metric_dump.rb │ ├── backend_parser.rb │ ├── backend_usage_dump.rb │ ├── limit_dump.rb │ ├── mapping_rule_dump.rb │ ├── method_dump.rb │ ├── metric_dump.rb │ ├── pricing_rule_dump.rb │ ├── product_deployment_parser.rb │ ├── product_dump.rb │ ├── product_parser.rb │ └── remote.rb │ ├── entities.rb │ ├── entities │ ├── account.rb │ ├── activedocs.rb │ ├── application.rb │ ├── application_plan.rb │ ├── backend.rb │ ├── backend_mapping_rule.rb │ ├── backend_method.rb │ ├── backend_metric.rb │ ├── backend_usage.rb │ ├── base_entity.rb │ ├── limit.rb │ ├── mapping_rule.rb │ ├── method.rb │ ├── metric.rb │ ├── pricing_rule.rb │ ├── proxy_config.rb │ └── service.rb │ ├── error.rb │ ├── helper.rb │ ├── openapi.rb │ ├── openapi │ ├── oas3.rb │ └── swagger.rb │ ├── proxy_logger.rb │ ├── remote_cache.rb │ ├── remotes.rb │ ├── resource_reader.rb │ └── version.rb ├── licenses.xml ├── resources ├── oas3_meta_schema.json └── swagger_meta_schema.json └── spec ├── custom_matchers.rb ├── helpers.rb ├── integration └── commands │ ├── activedocs_command │ ├── apply_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ └── list_command_spec.rb │ ├── applications_command │ ├── apply_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ ├── list_command_spec.rb │ └── show_command_spec.rb │ ├── backend_command │ └── copy_backend_spec.rb │ ├── import_command │ └── openapi │ │ ├── backend_api_spec.rb │ │ ├── backend_import_basic_spec.rb │ │ ├── basepath_spec.rb │ │ ├── basic_spec.rb │ │ ├── oidc_spec.rb │ │ └── prefix_matching_spec.rb │ ├── methods_command │ ├── apply_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ └── list_command_spec.rb │ ├── metrics_command │ ├── apply_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ └── list_command_spec.rb │ ├── plans_command │ ├── apply_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ ├── list_command_spec.rb │ ├── plan_export_spec.rb │ ├── plan_import_spec.rb │ └── show_command_spec.rb │ ├── product_command │ ├── copy_command_spec.rb │ ├── export_command_spec.rb │ └── import_command_spec.rb │ ├── proxy_config_command │ ├── list_command_spec.rb │ ├── promote_command_spec.rb │ └── show_command_spec.rb │ ├── remote_command │ ├── remote_add_spec.rb │ ├── remote_list_spec.rb │ ├── remote_remove_spec.rb │ └── remote_rename_spec.rb │ └── service_command │ ├── apply_command_spec.rb │ ├── copy_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ ├── list_command_spec.rb │ └── show_command_spec.rb ├── resources ├── 3scale_toolbox_plugin_template.erb ├── oidc.yaml ├── petstore.yaml ├── plan.yaml ├── product_cr.yaml ├── valid_config_file.yaml └── valid_swagger.yaml ├── shared_contexts.rb ├── shared_examples.rb ├── shared_oas3_contexts.rb ├── shared_swagger_contexts.rb ├── spec_helper.rb └── unit ├── 3scale_client_factory_spec.rb ├── 3scale_toolbox_spec.rb ├── attribute_filters └── service_id_from_ref_filter_spec.rb ├── cli └── custom_table_printer_spec.rb ├── command_hierarchy_spec.rb ├── commands ├── activedocs_command │ ├── apply_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ └── list_command_spec.rb ├── application_command │ ├── apply_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ ├── list_command_spec.rb │ └── show_command_spec.rb ├── backend_command │ ├── copy_command │ │ ├── copy_mapping_rules_task_spec.rb │ │ ├── copy_methods_task_spec.rb │ │ ├── copy_metrics_task_spec.rb │ │ ├── create_or_update_target_backend_task_spec.rb │ │ └── delete_mapping_rules_task_spec.rb │ └── copy_command_spec.rb ├── import_command │ ├── openapi │ │ ├── create_activedocs_step_spec.rb │ │ ├── create_backend_method_step_spec.rb │ │ ├── create_backend_step_spec.rb │ │ ├── create_mapping_backend_rules_step_spec.rb │ │ ├── create_mapping_rules_step_spec.rb │ │ ├── create_methods_step_spec.rb │ │ ├── create_service_step_spec.rb │ │ ├── import_backend_step_spec.rb │ │ ├── import_product_step_spec.rb │ │ ├── mapping_rule_spec.rb │ │ ├── method_spec.rb │ │ ├── step_spec.rb │ │ ├── update_policies_spec.rb │ │ ├── update_service_oidc_conf_spec.rb │ │ └── update_service_proxy_spec.rb │ └── openapi_spec.rb ├── methods_command │ ├── apply_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ └── list_command_spec.rb ├── metrics_command │ ├── apply_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ └── list_command_spec.rb ├── plans_command │ ├── apply_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ ├── export_command_spec.rb │ ├── import │ │ ├── create_or_update_app_plan_step_spec.rb │ │ ├── import_backend_metrics_step_spec.rb │ │ ├── import_plan_features_step_spec.rb │ │ ├── import_plan_limits_step_spec.rb │ │ ├── import_plan_metrics_step_spec.rb │ │ ├── import_plan_pricing_rules_step_spec.rb │ │ └── validation_plan_step_spec.rb │ ├── list_command_spec.rb │ └── show_command_spec.rb ├── policies_command │ ├── export_command_spec.rb │ └── import_command_spec.rb ├── policy_registry_command │ └── copy_command_spec.rb ├── product_command │ ├── copy_command │ │ ├── copy_backends_task_spec.rb │ │ └── delete_target_backend_usages_task_spec.rb │ ├── copy_command_spec.rb │ ├── export_command_spec.rb │ └── import_command_spec.rb ├── proxy_command │ ├── deploy_command_spec.rb │ ├── show_command_spec.rb │ └── update_command_spec.rb ├── proxy_config_command │ ├── deploy_command_spec.rb │ ├── export_command_spec.rb │ ├── list_command_spec.rb │ ├── promote_command_spec.rb │ └── show_command_spec.rb └── service_command │ ├── apply_command_spec.rb │ ├── copy_command │ ├── copy_activedocs_spec.rb │ ├── copy_app_plans_task_spec.rb │ ├── copy_limits_task_spec.rb │ ├── copy_mapping_rules_task_spec.rb │ ├── copy_methods_task_spec.rb │ ├── copy_metrics_task_spec.rb │ ├── copy_policies_task_spec.rb │ ├── copy_pricingrules_task_spec.rb │ ├── copy_service_proxy_task_spec.rb │ ├── create_or_update_service_task_spec.rb │ └── destroy_mapping_rules_task_spec.rb │ ├── copy_command_spec.rb │ ├── create_command_spec.rb │ ├── delete_command_spec.rb │ ├── list_command_spec.rb │ └── show_command_spec.rb ├── configuration_spec.rb ├── crds ├── backend_parser_spec.rb ├── product_parser_spec.rb └── remote_spec.rb ├── entities ├── account_spec.rb ├── activedocs_spec.rb ├── application_plan_spec.rb ├── application_spec.rb ├── backend_mapping_rule_spec.rb ├── backend_method_spec.rb ├── backend_metric_spec.rb ├── backend_spec.rb ├── backend_usage_spec.rb ├── limit_spec.rb ├── mapping_rule_spec.rb ├── method_spec.rb ├── metric_spec.rb ├── pricing_rule_spec.rb ├── proxy_config_spec.rb └── service_spec.rb ├── error_handler_spec.rb ├── helper_spec.rb ├── openapi ├── oas3_spec.rb └── swagger_spec.rb ├── plugin_spec.rb ├── proxy_logger_spec.rb ├── remote_cache_spec.rb ├── remotes_spec.rb └── resource_reader_spec.rb /.circleci/setup-rubygems.sh: -------------------------------------------------------------------------------- 1 | mkdir ~/.gem 2 | echo -e "---\r\n:rubygems_api_key: $RUBYGEMS_API_KEY" > ~/.gem/credentials 3 | chmod 0600 /home/circleci/.gem/credentials 4 | -------------------------------------------------------------------------------- /.github/workflows/fast-forward-branch.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Fast-forward between branches 3 | "on": 4 | workflow_dispatch: 5 | inputs: 6 | ref: 7 | description: 'The branch name or commit to fast-forward from' 8 | default: 'main' 9 | type: string 10 | to_branch: 11 | description: 'The branch name to fast-forward to' 12 | default: 'managed-services' 13 | type: string 14 | jobs: 15 | fast-forward: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | ref: ${{ github.event.inputs.ref }} 21 | fetch-depth: 0 22 | - run: | 23 | git fetch origin ${{ github.event.inputs.ref }}:${{ github.event.inputs.to_branch }} 24 | git push origin ${{ github.event.inputs.to_branch }} 25 | -------------------------------------------------------------------------------- /.github/workflows/tagRelease.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow that is manually triggered to create a tag release in the repo in which it is present. 2 | # It can be manually triggered by those with permissions, or triggered from the main action which can trigger tag releases for all repos at once. 3 | 4 | name: tagRelease 5 | 6 | on: 7 | workflow_dispatch: 8 | # Inputs the workflow accepts. 9 | inputs: 10 | tag: 11 | # Friendly description to be shown in the UI instead of 'tag' 12 | description: 'Tag to be created along with release' 13 | # Input has to be provided for the workflow to run 14 | required: true 15 | git_ref: 16 | description: 'Git reference to create tag from, can be a commit hash or branch' 17 | required: true 18 | 19 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 20 | jobs: 21 | createRelease: 22 | runs-on: ubuntu-latest 23 | name: Create Release 24 | steps: 25 | - uses: softprops/action-gh-release@v1 #This action will create the release with the params specified. 26 | with: 27 | tag_name: ${{ inputs.tag }} 28 | target_commitish: ${{ inputs.git_ref }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /vendor/ 8 | /spec/reports/ 9 | /spec/examples.txt 10 | /tmp/ 11 | .DS_Store 12 | .env 13 | /crash.log 14 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | dist: bionic 3 | arch: 4 | - ppc64le 5 | - s390x 6 | os: linux 7 | 8 | before_install: 9 | - .travis/setup_${TRAVIS_OS_NAME}_env.sh 10 | 11 | install: 12 | - .travis/install_${TRAVIS_OS_NAME}_env.sh 13 | 14 | script: 15 | - .travis/run_${TRAVIS_OS_NAME}_tests.sh 16 | -------------------------------------------------------------------------------- /.travis/install_linux_env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ev 4 | 5 | bundle config --local set path 'vendor/bundle' 6 | bundle install --jobs=3 --retry=3 7 | -------------------------------------------------------------------------------- /.travis/run_linux_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ev 4 | 5 | # unit tests 6 | bundle exec rake spec:unit 7 | 8 | # integration tests 9 | bundle exec rake spec:integration 10 | -------------------------------------------------------------------------------- /.travis/setup_linux_env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ev 4 | 5 | gem install bundler --version 2.3.5 6 | -------------------------------------------------------------------------------- /3scale_toolbox.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | lib = File.expand_path('../lib', __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require '3scale_toolbox/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = '3scale_toolbox' 9 | spec.version = ThreeScaleToolbox::VERSION 10 | spec.licenses = ['MIT'] 11 | spec.authors = ['Miguel Soriano', 'Eguzki Astiz Lezaun'] 12 | spec.email = ['msoriano@redhat.com', 'eastizle@redhat.com'] 13 | 14 | spec.summary = %q{3scale Toolbox.} 15 | spec.description = %q{3scale tools to manage your API from the terminal.} 16 | spec.homepage = 'https://github.com/3scale/3scale_toolbox' 17 | 18 | spec.files = Dir['{lib}/**/*.rb'] 19 | spec.files += Dir['{exe,resources}/*'] 20 | spec.files << 'README.md' 21 | spec.files << 'licenses.xml' 22 | spec.files << 'LICENSE' 23 | spec.files << 'NOTICE' 24 | # There is a bug in gem 2.7.6 and __FILE__ cannot be used. 25 | # It is expanded in rake release task with full path on the building host 26 | spec.files << '3scale_toolbox.gemspec' 27 | 28 | spec.bindir = 'exe' 29 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 30 | spec.require_paths = ['lib'] 31 | 32 | spec.add_development_dependency 'bundler' 33 | spec.add_development_dependency 'dotenv' 34 | spec.add_development_dependency 'rake', '~> 13.0' 35 | spec.add_development_dependency 'rspec', '~> 3.8' 36 | spec.add_development_dependency 'webmock', '~> 3.4' 37 | spec.required_ruby_version = '>= 3.0' 38 | 39 | spec.add_dependency '3scale-api', '~> 1.6' 40 | spec.add_dependency 'cri', '~> 2.15' 41 | spec.add_dependency 'json-schema', '~> 2.8' 42 | spec.add_dependency 'oas_parser', '~> 0.20' 43 | spec.add_dependency 'activesupport', '>= 6', '< 8' 44 | end 45 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi9/ruby-30 2 | MAINTAINER Eguzki Astiz Lezaun 3 | 4 | USER root 5 | 6 | RUN gem install bundler --version 2.3.5 --no-document 7 | 8 | WORKDIR /usr/src/app 9 | COPY . . 10 | 11 | RUN chmod +t /tmp 12 | 13 | RUN bundle config --local silence_root_warning 1 \ 14 | && bundle config --local disable_shared_gems 1 \ 15 | && bundle config --local without "development test" \ 16 | && bundle config set --local deployment 'true' \ 17 | && bundle config --local gemfile Gemfile \ 18 | && bundle config set --local path 'vendor/bundle' 19 | 20 | RUN bundle install \ 21 | && bundle binstubs 3scale_toolbox 22 | 23 | ENV PATH="/usr/src/app/bin:${PATH}" 24 | 25 | # Drop privileges 26 | USER default 27 | 28 | WORKDIR /opt/app-root/src 29 | 30 | CMD ["/bin/bash"] 31 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in 3scale.gemspec 4 | gemspec 5 | 6 | group :development do 7 | gem 'license_finder', '~> 7.2' 8 | gem 'pry' 9 | # rubyzip is a transitive depencency from license_finder with vulnerability on < 1.3.0 10 | gem 'rubyzip', '>= 1.3.0' 11 | end 12 | 13 | group :test do 14 | gem 'codecov', require: false 15 | end 16 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Red Hat 3scale Toolbox 2 | Copyright (c) 2010-2016 3scale, Inc 3 | Copyright (c) 2016-2019 Red Hat, Inc. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | begin 4 | require 'rspec/core/rake_task' 5 | namespace :spec do 6 | RSpec::Core::RakeTask.new(:integration) do |t| 7 | t.pattern = 'spec/integration/**/*_spec.rb' 8 | end 9 | RSpec::Core::RakeTask.new(:unit) do |t| 10 | t.pattern = 'spec/unit/**/*_spec.rb' 11 | end 12 | RSpec::Core::RakeTask.new(:all) do |t| 13 | t.pattern = 'spec/**/*_spec.rb' 14 | end 15 | end 16 | rescue LoadError 17 | warn 'RSpec is not installed!' 18 | end 19 | 20 | namespace :license_finder do 21 | DECISION_FILE = "#{File.dirname(__FILE__)}/.dependency_decisions.yml".freeze 22 | 23 | desc 'Check license compliance of dependencies' 24 | task :check do 25 | STDOUT.puts "Checking license compliance\n" 26 | unless system("license_finder --decisions-file=#{DECISION_FILE}") 27 | STDERR.puts "\n*** License compliance test failed ***\n" 28 | exit 1 29 | end 30 | end 31 | 32 | desc 'Generate an CSV report for licenses' 33 | task :report do 34 | system("license_finder report --decisions-file=#{DECISION_FILE} --quiet --format=xml") 35 | end 36 | end 37 | 38 | task default: 'spec:unit' 39 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'threescale_toolbox' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | require 'pry' 10 | Pry.start 11 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | gem install bundler --version '~> 1.11' 7 | bundle install 8 | 9 | # Do any other automated setup that you need to do here 10 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you want to contribute to the 3scale Toolbox project and make it better, 4 | your help is very welcome. 5 | 6 | ## Open issues 7 | 8 | Start by checking the list of issues in GitHub. Maybe your idea was discussed in the past or is part of an ongoing conversation. 9 | 10 | In case it is a new idea for enhancement, a bug fix, a question or whatever unprecedented contribution you want to share, before sending a pull-request, please make sure to describe the issue so we can have a conversation together and help you fin dthe best way to get your contribution merged. 11 | 12 | ## How to make a clean pull request 13 | 14 | * Create a personal [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) of the project on Github. 15 | * Clone the fork on your local machine. Your remote repo on Github is called `origin`. 16 | * Create a new branch to work on! Branch from `main`. 17 | * Implement/fix your feature, comment your code. 18 | * Write or adapt tests as needed. 19 | * [Run tests](/README.md#Testing) locally. 20 | * Add or change the documentation as needed. 21 | * [Sign](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification/signing-commits) your commits (recommended but not required). 22 | * Push your branch to your fork on Github, the remote `origin`. 23 | * From your fork open a pull request in the correct branch. Target the project's `main` branch. 24 | * If the maintainer requests further changes just push them to your branch. The PR will be updated automatically. 25 | 26 | ## Additional resources to contributors 27 | 28 | * [Development](/README.md#Development) 29 | * [Plugins](/README.md#Plugins) 30 | -------------------------------------------------------------------------------- /docs/copy-policy-registry.md: -------------------------------------------------------------------------------- 1 | ## Copy Policy Registry 2 | 3 | Toolbox command to copy policy registry (a.k.a. `custom policies`) from a source account to a target account. 4 | * Missing custom policies are being created in the target account. 5 | * Matching custom policies are being updated in the target account. 6 | * This copy command is idempotent. 7 | 8 | Missing custom policies are defined as custom policies that exist in the source account and do not exist in the account tenant. 9 | 10 | Matching custom policies are defined as custom policies that exist in both source and target accounts. 11 | 12 | ```shell 13 | NAME 14 | copy - Copy policy registry 15 | 16 | USAGE 17 | 3scale policy-registry copy [opts] 18 | 19 | 20 | DESCRIPTION 21 | Copy policy registry 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/import-csv.md: -------------------------------------------------------------------------------- 1 | ### Import from CSV 2 | 3 | Will create new services, metrics, methods, and mapping rules having as source comma separated values (CSV) formatted file. 4 | 5 | 3scale instances can be either a [URL](docs/remotes.md#remote-urls) or the name of a [remote](docs/remotes.md). 6 | 7 | CSV header 8 | 9 | ```csv 10 | service_name,endpoint_name,endpoint_http_method,endpoint_path,auth_mode,endpoint_system_name,type 11 | ``` 12 | 13 | File example 14 | 15 | ```csv 16 | service_name,endpoint_name,endpoint_http_method,endpoint_path,auth_mode,endpoint_system_name,type 17 | Movies ,Movies (Biography),GET,/movies/biography/,api_key,movies_biography,metric 18 | Movies ,Movies (Drama),GET,/movies/drama/,api_key,movies_drama,method 19 | ``` 20 | 21 | Help message: 22 | 23 | ```shell 24 | $ 3scale import csv -h 25 | NAME 26 | csv - Import csv file 27 | 28 | USAGE 29 | 3scale import csv [opts] -d -f 30 | 31 | DESCRIPTION 32 | Create new services, metrics, methods and mapping rules from CSV 33 | formatted file 34 | 35 | OPTIONS 36 | -d --destination= 3scale target instance. Url or remote name 37 | -f --file= CSV formatted file 38 | 39 | OPTIONS FOR IMPORT 40 | -h --help show help for this command 41 | -k --insecure Proceed and operate even for server 42 | connections otherwise considered insecure 43 | -v --version Prints the version of this command 44 | --verbose Verbose mode 45 | ``` 46 | 47 | Example: 48 | 49 | ```shell 50 | 3scale import csv --destination=https://provider_key@user-admin.3scale.net --file=examples/import_example.csv 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/proxy.md: -------------------------------------------------------------------------------- 1 | ## Proxy Configuration 2 | 3 | * [Deploy Proxy Configuration](#deploy) 4 | * [Update Proxy Configuration](#update) 5 | * [Show Proxy Configuration](#show) 6 | 7 | ### Deploy 8 | 9 | ```shell 10 | NAME 11 | deploy - Promotes the APIcast configuration to the Staging Environment 12 | 13 | USAGE 14 | 3scale proxy deploy 15 | 16 | DESCRIPTION 17 | Promotes the APIcast configuration to the Staging Environment (Production 18 | Environment in case of Service Mesh). 19 | 20 | OPTIONS 21 | -o --output= Output format. One of: json|yaml 22 | ``` 23 | 24 | ### Update 25 | 26 | Update APIcast configuration command. Only specified parameters will be updated. 27 | 28 | Check out 3scale API docs (*https://tenant-admin.3scale.example.com/admin/api-docs*) **Proxy Update** doc 29 | for a list of valid APIcast parameters. 30 | 31 | Example: 32 | 33 | ```shel 34 | 3scale proxy update supertest api -p credentials_location=query -p error_status_auth_failed=500 -p 'error_auth_failed=Authentication failed oohhh' 35 | ``` 36 | 37 | ```shell 38 | NAME 39 | update - Update APIcast configuration 40 | 41 | USAGE 42 | 3scale proxy update 43 | 44 | DESCRIPTION 45 | Update APIcast configuration 46 | 47 | OPTIONS 48 | -o --output= Output format. One of: json|yaml 49 | -p --param= APIcast configuration parameters. Format: 50 | [--param key=value]. Multiple options 51 | allowed. 52 | ``` 53 | 54 | ### Show 55 | 56 | ```shell 57 | NAME 58 | show - Fetch (undeployed) APIcast configuration 59 | 60 | USAGE 61 | 3scale proxy show 62 | 63 | DESCRIPTION 64 | Fetch (undeployed) APIcast configuration 65 | 66 | OPTIONS 67 | -o --output= Output format. One of: json|yaml 68 | ``` 69 | -------------------------------------------------------------------------------- /examples/import.csv: -------------------------------------------------------------------------------- 1 | service_name,endpoint_name,endpoint_http_method,endpoint_path,auth_mode,endpoint_system_name,type 2 | Movies ,Movies (Biography),GET,/movies/biography/,api_key,movies_biography,metric 3 | Movies ,Movies (Drama),GET,/movies/drama/,api_key,movies_drama,method 4 | Movies ,Movies (adventure),GET,/movies/adventure/,api_key,movies_adventure,metric 5 | Music,Music (Biography),GET,/music/biography/,api_key,music_biography,metric 6 | Music,Music (Post punk),GET,/music/postpunk/,api_key,music_postpunk,metric 7 | Music,Music (Sludge),GET,/music/sludge/,api_key,music_sludge,metric -------------------------------------------------------------------------------- /examples/petstore_simple.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: "3.0.2" 3 | info: 4 | title: "Petstore" 5 | description: "petstore API" 6 | version: "1.0.0" 7 | servers: 8 | - url: /api 9 | paths: 10 | /pet: 11 | get: 12 | operationId: "getPet" 13 | responses: 14 | 405: 15 | description: "invalid input" 16 | security: 17 | - petstore_api_key: [] 18 | components: 19 | securitySchemes: 20 | petstore_api_key: 21 | type: apiKey 22 | name: user_key 23 | in: query 24 | -------------------------------------------------------------------------------- /examples/petstore_simple_no_servers.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: "3.0.2" 3 | info: 4 | title: "Petstore" 5 | description: "petstore API" 6 | version: "1.0.0" 7 | paths: 8 | /pet: 9 | get: 10 | operationId: "getPet" 11 | responses: 12 | 405: 13 | description: "invalid input" 14 | security: 15 | - petstore_api_key: [] 16 | components: 17 | securitySchemes: 18 | petstore_api_key: 19 | type: apiKey 20 | name: user_key 21 | in: query 22 | -------------------------------------------------------------------------------- /exe/3scale: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | def suppress_warnings 4 | original_verbosity = $VERBOSE 5 | $VERBOSE = nil 6 | yield 7 | ensure 8 | $VERBOSE = original_verbosity 9 | end 10 | 11 | suppress_warnings { require '3scale_toolbox' } 12 | 13 | args = ARGV.clone 14 | 15 | exit(ThreeScaleToolbox::CLI.run(args)) 16 | -------------------------------------------------------------------------------- /lib/3scale_toolbox.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'cri' 3 | require 'yaml' 4 | require 'uri' 5 | require 'time' 6 | require 'csv' 7 | require 'net/http' 8 | require 'pathname' 9 | require 'oas_parser' 10 | require '3scale/api' 11 | require 'json-schema' 12 | require 'erb' 13 | require 'logger' 14 | 15 | require '3scale_toolbox/version' 16 | require '3scale_toolbox/helper' 17 | require '3scale_toolbox/error' 18 | require '3scale_toolbox/remote_cache' 19 | require '3scale_toolbox/proxy_logger' 20 | require '3scale_toolbox/resource_reader' 21 | require '3scale_toolbox/configuration' 22 | require '3scale_toolbox/remotes' 23 | require '3scale_toolbox/3scale_client_factory' 24 | require '3scale_toolbox/crds' 25 | require '3scale_toolbox/entities' 26 | require '3scale_toolbox/attribute_filters' 27 | require '3scale_toolbox/base_command' 28 | require '3scale_toolbox/openapi' 29 | require '3scale_toolbox/commands' 30 | require '3scale_toolbox/cli' 31 | 32 | module ThreeScaleToolbox 33 | def self.load_plugins 34 | plugin_paths.each { |plugin_path| require plugin_path } 35 | end 36 | 37 | def self.plugin_paths 38 | Gem.find_files('3scale_toolbox_plugin') 39 | end 40 | 41 | def self.default_config_file 42 | # THREESCALE_CLI_CONFIG env var has priority over $HOME/.3scalerc.yaml file 43 | ENV['THREESCALE_CLI_CONFIG'] || File.join(Gem.user_home, '.3scalerc.yaml') 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/3scale_client_factory.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | class ThreeScaleClientFactory 3 | class << self 4 | def get(remotes, remote_str, verify_ssl, verbose = false, keep_alive = true) 5 | new(remotes, remote_str, verify_ssl, verbose, keep_alive).call 6 | end 7 | end 8 | 9 | attr_reader :remotes, :remote_str, :verify_ssl, :verbose, :keep_alive 10 | 11 | def initialize(remotes, remote_str, verify_ssl, verbose, keep_alive) 12 | @remotes = remotes 13 | @remote_str = remote_str 14 | @verify_ssl = verify_ssl 15 | @verbose = verbose 16 | @keep_alive = keep_alive 17 | end 18 | 19 | def call 20 | begin 21 | remote = Remotes.from_uri(remote_str) 22 | rescue InvalidUrlError 23 | remote = remotes.fetch(remote_str) 24 | end 25 | 26 | client = remote_client(**remote.merge(verify_ssl: verify_ssl, keep_alive: keep_alive)) 27 | client = ProxyLogger.new(client) if verbose 28 | RemoteCache.new(client) 29 | end 30 | 31 | private 32 | 33 | def remote_client(endpoint:, authentication:, verify_ssl:, keep_alive:) 34 | ThreeScale::API.new(endpoint: endpoint, provider_key: authentication, 35 | verify_ssl: verify_ssl, keep_alive: keep_alive) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/attribute_filters.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/attribute_filters/attribute_filter' 2 | require '3scale_toolbox/attribute_filters/service_id_from_ref_filter' -------------------------------------------------------------------------------- /lib/3scale_toolbox/attribute_filters/attribute_filter.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module AttributeFilters 3 | module AttributeFilter 4 | def filter(enumerable) 5 | raise "Not implemented" 6 | end 7 | end 8 | end 9 | end -------------------------------------------------------------------------------- /lib/3scale_toolbox/attribute_filters/service_id_from_ref_filter.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module AttributeFilters 3 | class ServiceIDFilterFromServiceRef 4 | include AttributeFilter 5 | 6 | attr_reader :remote, :service_ref, :service_id_key 7 | 8 | def initialize(remote, service_ref, service_id_key) 9 | @remote = remote 10 | @service_ref = service_ref 11 | @service_id_key = service_id_key 12 | end 13 | 14 | def filter(enumerable) 15 | svc_id = find_service 16 | enumerable.select { |e| e.key?(service_id_key) && e[service_id_key].to_s == svc_id.to_s } 17 | end 18 | 19 | private 20 | 21 | def find_service 22 | svc_id = -1 23 | Entities::Service.find(remote: remote, ref: service_ref).tap do |svc| 24 | svc_id = svc.id if !svc.nil? 25 | end 26 | svc_id 27 | end 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /lib/3scale_toolbox/base_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Command 3 | def self.included(base) 4 | base.extend(ClassMethods) 5 | end 6 | 7 | module ClassMethods 8 | def subcommands 9 | @subcommands ||= [] 10 | end 11 | 12 | def add_subcommand(command) 13 | subcommands << command 14 | end 15 | 16 | ## 17 | # Override to command 18 | # 19 | def command 20 | raise Exception, 'base command has no command definition' 21 | end 22 | 23 | ## 24 | # Iterate recursively over command tree 25 | # 26 | def build_command 27 | subcommands.each_with_object(command) do |subcommand, root_command| 28 | root_command.add_command(subcommand.build_command) 29 | end 30 | end 31 | end 32 | 33 | def config 34 | @config ||= ThreeScaleToolbox::Configuration.new(config_file) 35 | end 36 | 37 | def config_file 38 | options[:'config-file'] 39 | end 40 | 41 | def remotes 42 | @remotes ||= Remotes.new(config) 43 | end 44 | 45 | ## 46 | # Input param can be endpoint url or remote name 47 | # 48 | def threescale_client(str) 49 | ThreeScaleClientFactory.get(remotes, str, verify_ssl, verbose, keep_alive) 50 | end 51 | 52 | def verify_ssl 53 | # this is flag. It is either true or false. Cannot be nil 54 | !options[:insecure] 55 | end 56 | 57 | def verbose 58 | options[:verbose] 59 | end 60 | 61 | def keep_alive 62 | !options[:'disable-keep-alive'] 63 | end 64 | 65 | def exit_with_message(message) 66 | raise ThreeScaleToolbox::Error, message 67 | end 68 | 69 | def fetch_required_option(key) 70 | options.fetch(key) { exit_with_message "error: Missing argument #{key}" } 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/cli.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/cli/error_handler' 2 | require '3scale_toolbox/cli/null_printer' 3 | require '3scale_toolbox/cli/json_printer' 4 | require '3scale_toolbox/cli/yaml_printer' 5 | require '3scale_toolbox/cli/custom_table_printer' 6 | require '3scale_toolbox/cli/output_flag' 7 | 8 | module ThreeScaleToolbox::CLI 9 | def self.root_command 10 | ThreeScaleToolbox::Commands::ThreeScaleCommand 11 | end 12 | 13 | def self.add_command(command) 14 | root_command.add_subcommand(command) 15 | end 16 | 17 | def self.load_builtin_commands 18 | ThreeScaleToolbox::Commands::BUILTIN_COMMANDS.each(&method(:add_command)) 19 | end 20 | 21 | def self.install_signal_handlers 22 | # Set exit handler 23 | # Only OS supported signals 24 | available_signals = %w[INT TERM].select { |signal_name| Signal.list.key? signal_name } 25 | available_signals.each do |signal| 26 | Signal.trap(signal) do 27 | puts 28 | exit!(0) 29 | end 30 | end 31 | 32 | # Set stack trace dump handler 33 | if !defined?(RUBY_ENGINE) || RUBY_ENGINE != 'jruby' 34 | if Signal.list.key? 'USR1' 35 | Signal.trap('USR1') do 36 | puts 'Caught USR1; dumping a stack trace' 37 | puts caller.map { |i| " #{i}" }.join("\n") 38 | end 39 | end 40 | end 41 | end 42 | 43 | def self.run(args) 44 | install_signal_handlers 45 | err = ErrorHandler.error_watchdog do 46 | load_builtin_commands 47 | ThreeScaleToolbox.load_plugins 48 | root_command.build_command.run args 49 | end 50 | err.nil? ? 0 : 1 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/cli/custom_table_printer.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CLI 3 | class CustomTablePrinter 4 | attr_reader :fields 5 | 6 | def initialize(fields) 7 | @fields = fields 8 | end 9 | 10 | def print_record(record) 11 | print_collection([record]) 12 | end 13 | 14 | def print_collection(collection) 15 | print_header 16 | print_data(collection) 17 | end 18 | 19 | private 20 | 21 | def print_header 22 | puts fields.map(&:upcase).join("\t") 23 | end 24 | 25 | def print_data(collection) 26 | collection.each do |obj| 27 | puts fields.map { |field| obj.fetch(field, '(empty)').to_s }.join("\t") 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/cli/json_printer.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CLI 3 | class JsonPrinter 4 | def print_record(record) 5 | puts JSON.pretty_generate(record) 6 | end 7 | 8 | def print_collection(collection) 9 | puts JSON.pretty_generate(collection) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/cli/null_printer.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CLI 3 | class NullPrinter 4 | def print_record(record) 5 | end 6 | 7 | def print_collection(collection) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/cli/output_flag.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CLI 3 | class PrinterTransformer 4 | def call(output_format) 5 | raise unless %w[yaml json].include?(output_format) 6 | 7 | case output_format 8 | when 'yaml' 9 | YamlPrinter.new 10 | when 'json' 11 | JsonPrinter.new 12 | end 13 | end 14 | end 15 | 16 | def self.output_flag(dsl) 17 | dsl.option :o, :output, 'Output format. One of: json|yaml', argument: :required, transform: PrinterTransformer.new 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/cli/yaml_printer.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CLI 3 | class YamlPrinter 4 | def print_record(record) 5 | puts YAML.dump(record) 6 | end 7 | 8 | def print_collection(collection) 9 | puts YAML.dump(collection) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/3scale_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ThreeScaleCommand 4 | include ThreeScaleToolbox::Command 5 | 6 | def self.command 7 | Cri::Command.define do 8 | name '3scale' 9 | usage '3scale [options]' 10 | summary '3scale toolbox' 11 | description '3scale toolbox to manage your API from the terminal.' 12 | option :c, 'config-file', '3scale toolbox configuration file', 13 | argument: :required, default: ThreeScaleToolbox.default_config_file 14 | flag :v, :version, 'Prints the version of this command' do 15 | puts ThreeScaleToolbox::VERSION 16 | exit 0 17 | end 18 | flag :k, :insecure, 'Proceed and operate even for server connections otherwise considered insecure' 19 | flag nil, :verbose, 'Verbose mode' 20 | flag nil, :'disable-keep-alive', 'Disable keep alive HTTP connection mode' 21 | flag :h, :help, 'show help for this command' do |_, cmd| 22 | puts cmd.help 23 | exit 0 24 | end 25 | 26 | run do |_opts, _args, cmd| 27 | puts cmd.help 28 | end 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/account_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/account_command/find_command' 2 | 3 | module ThreeScaleToolbox 4 | module Commands 5 | module AccountCommand 6 | include ThreeScaleToolbox::Command 7 | 8 | def self.command 9 | Cri::Command.define do 10 | name 'account' 11 | usage 'acccount [options]' 12 | summary 'account super command' 13 | description 'Accounts commands' 14 | 15 | run do |_opts, _args, cmd| 16 | puts cmd.help 17 | end 18 | end 19 | end 20 | add_subcommand(Find::FindSubcommand) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/account_command/find_command.rb: -------------------------------------------------------------------------------- 1 | require 'cri' 2 | require '3scale_toolbox/base_command' 3 | 4 | module ThreeScaleToolbox 5 | module Commands 6 | module AccountCommand 7 | module Find 8 | class FindSubcommand < Cri::CommandRunner 9 | include ThreeScaleToolbox::Command 10 | 11 | def self.command 12 | Cri::Command.define do 13 | name 'find' 14 | usage 'find [opts] ' 15 | summary 'find account' 16 | description 'Find account by email, provider key or service token' 17 | 18 | option :a, :'print-all', 'Print all the account info', argument: :forbidden 19 | param :remote 20 | param :text 21 | 22 | runner FindSubcommand 23 | end 24 | end 25 | 26 | def run 27 | client = threescale_client(arguments[:remote]) 28 | account = ThreeScaleToolbox::Entities::Account.find_by_text(arguments[:text], client) 29 | if account.nil? 30 | puts 'Account not found' 31 | return 32 | end 33 | 34 | account.verbose = options[:'print-all'] 35 | puts account 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/activedocs_command.rb: -------------------------------------------------------------------------------- 1 | require 'cri' 2 | require '3scale_toolbox/base_command' 3 | require '3scale_toolbox/commands/activedocs_command/delete_command' 4 | require '3scale_toolbox/commands/activedocs_command/create_command' 5 | require '3scale_toolbox/commands/activedocs_command/apply_command' 6 | require '3scale_toolbox/commands/activedocs_command/list_command' 7 | 8 | module ThreeScaleToolbox 9 | module Commands 10 | module ActiveDocsCommand 11 | include ThreeScaleToolbox::Command 12 | 13 | def self.command 14 | Cri::Command.define do 15 | name 'activedocs' 16 | usage 'activedocs [options]' 17 | summary 'activedocs super command' 18 | description 'Manage your ActiveDocs' 19 | 20 | run do |_opts, _args, cmd| 21 | puts cmd.help 22 | end 23 | end 24 | end 25 | 26 | add_subcommand(Delete::DeleteSubcommand) 27 | add_subcommand(Create::CreateSubcommand) 28 | add_subcommand(Apply::ApplySubcommand) 29 | add_subcommand(List::ListSubcommand) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/activedocs_command/delete_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ActiveDocsCommand 4 | module Delete 5 | class DeleteSubcommand < Cri::CommandRunner 6 | include ThreeScaleToolbox::Command 7 | 8 | def self.command 9 | Cri::Command.define do 10 | name 'delete' 11 | usage 'delete ' 12 | summary 'Delete an ActiveDocs' 13 | description 'Remove an ActiveDocs' 14 | runner DeleteSubcommand 15 | 16 | param :remote 17 | param :activedocs_id_or_system_name 18 | end 19 | end 20 | 21 | def run 22 | activedocs.delete 23 | puts "ActiveDocs with id: #{activedocs.id} deleted" 24 | end 25 | 26 | private 27 | 28 | def remote 29 | @remote ||= threescale_client(arguments[:remote]) 30 | end 31 | 32 | def ref 33 | arguments[:activedocs_id_or_system_name] 34 | end 35 | 36 | def activedocs 37 | @activedocs ||= find_activedocs 38 | end 39 | 40 | def find_activedocs 41 | Entities::ActiveDocs.find(remote: remote, ref: ref).tap do |activedoc| 42 | raise ThreeScaleToolbox::Error, "ActiveDocs #{ref} does not exist" if activedoc.nil? 43 | end 44 | end 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/application_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/application_command/list_command' 2 | require '3scale_toolbox/commands/application_command/create_command' 3 | require '3scale_toolbox/commands/application_command/show_command' 4 | require '3scale_toolbox/commands/application_command/apply_command' 5 | require '3scale_toolbox/commands/application_command/delete_command' 6 | 7 | module ThreeScaleToolbox 8 | module Commands 9 | module ApplicationCommand 10 | include ThreeScaleToolbox::Command 11 | def self.command 12 | Cri::Command.define do 13 | name 'application' 14 | usage 'application [options]' 15 | summary 'application super command' 16 | description 'application commands' 17 | 18 | run do |_opts, _args, cmd| 19 | puts cmd.help 20 | end 21 | end 22 | end 23 | add_subcommand(List::ListSubcommand) 24 | add_subcommand(Create::CreateSubcommand) 25 | add_subcommand(Show::ShowSubcommand) 26 | add_subcommand(Apply::ApplySubcommand) 27 | add_subcommand(Delete::DeleteSubcommand) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/application_command/delete_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ApplicationCommand 4 | module Delete 5 | class DeleteSubcommand < Cri::CommandRunner 6 | include ThreeScaleToolbox::Command 7 | 8 | def self.command 9 | Cri::Command.define do 10 | name 'delete' 11 | usage 'delete [opts] ' 12 | summary 'delete application' 13 | description <<-HEREDOC 14 | Delete application' 15 | \n Application param allows: 16 | \n * User_key (API key) 17 | \n * App_id (from app_id/app_key pair) or Client ID (for OAuth and OpenID Connect authentication modes) 18 | \n * Application internal id 19 | HEREDOC 20 | 21 | param :remote 22 | param :application_ref 23 | 24 | runner DeleteSubcommand 25 | end 26 | end 27 | 28 | def run 29 | application.delete 30 | puts "Application id: #{application.id} deleted" 31 | end 32 | 33 | private 34 | 35 | def application 36 | @application ||= find_application 37 | end 38 | 39 | def find_application 40 | Entities::Application.find(remote: remote, ref: application_ref).tap do |app| 41 | raise ThreeScaleToolbox::Error, "Application #{application_ref} does not exist" if app.nil? 42 | end 43 | end 44 | 45 | def remote 46 | @remote ||= threescale_client(arguments[:remote]) 47 | end 48 | 49 | def application_ref 50 | arguments[:application_ref] 51 | end 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/backend_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/backend_command/copy_command' 2 | 3 | module ThreeScaleToolbox 4 | module Commands 5 | module BackendCommand 6 | include ThreeScaleToolbox::Command 7 | def self.command 8 | Cri::Command.define do 9 | name 'backend' 10 | usage 'backend [options]' 11 | summary 'backend super command' 12 | description 'Backend commands' 13 | 14 | run do |_opts, _args, cmd| 15 | puts cmd.help 16 | end 17 | end 18 | end 19 | add_subcommand(CopySubcommand) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/backend_command/copy_command/copy_mapping_rules_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module BackendCommand 4 | module CopyCommand 5 | class CopyMappingRulesTask 6 | include Task 7 | 8 | # entrypoint 9 | def run 10 | missing_rules.each do |mapping_rule| 11 | mr_attrs = mapping_rule.attrs.merge('metric_id' => metrics_map.fetch(mapping_rule.metric_id)) 12 | Entities::BackendMappingRule.create(backend: target_backend, attrs: mr_attrs) 13 | end 14 | logger.info "created #{missing_rules.size} mapping rules" 15 | report['missing_mapping_rules_created'] = missing_rules.size 16 | end 17 | 18 | private 19 | 20 | def metrics_map 21 | @metrics_map ||= source_backend.metrics_mapping(target_backend) 22 | end 23 | 24 | def missing_rules 25 | @missing_rules ||= ThreeScaleToolbox::Helper.array_difference(source_backend.mapping_rules, target_backend.mapping_rules) do |source_rule, target_rule| 26 | source_rule.pattern == target_rule.pattern && 27 | source_rule.http_method == target_rule.http_method && 28 | source_rule.delta == target_rule.delta && 29 | metrics_map.fetch(source_rule.metric_id) == target_rule.metric_id 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/backend_command/copy_command/copy_methods_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module BackendCommand 4 | module CopyCommand 5 | class CopyMethodsTask 6 | include Task 7 | 8 | # entrypoint 9 | def run 10 | missing_methods.each(&method(:create_method)) 11 | logger.info "created #{missing_methods.size} missing methods" 12 | report['missing_methods_created'] = missing_methods.size 13 | end 14 | 15 | private 16 | 17 | def create_method(method) 18 | Entities::BackendMethod.create(backend: target_backend, attrs: method.attrs) 19 | rescue ThreeScaleToolbox::ThreeScaleApiError => e 20 | raise e unless ThreeScaleToolbox::Helper.system_name_already_taken_error?(e.apierrors) 21 | 22 | warn "[WARN] backend method #{method.system_name} not created. " \ 23 | 'Backend metric with the same system_name exists.' 24 | end 25 | 26 | def missing_methods 27 | @missing_methods ||= ThreeScaleToolbox::Helper.array_difference(source_backend.methods, target_backend.methods) do |source, target| 28 | source.system_name == target.system_name 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/backend_command/copy_command/copy_metrics_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module BackendCommand 4 | module CopyCommand 5 | class CopyMetricsTask 6 | include Task 7 | 8 | # entrypoint 9 | def run 10 | missing_metrics.each(&method(:create_metric)) 11 | logger.info "created #{missing_metrics.size} missing metrics" 12 | report['missing_metrics_created'] = missing_metrics.size 13 | end 14 | 15 | private 16 | 17 | def create_metric(metric) 18 | Entities::BackendMetric.create(backend: target_backend, attrs: metric.attrs) 19 | end 20 | 21 | def missing_metrics 22 | @missing_metrics ||= ThreeScaleToolbox::Helper.array_difference(source_backend.metrics, target_backend.metrics) do |s_m, t_m| 23 | s_m.system_name == t_m.system_name 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/backend_command/copy_command/create_or_update_target_backend_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module BackendCommand 4 | module CopyCommand 5 | class CreateOrUpdateTargetBackendTask 6 | include Task 7 | 8 | # entrypoint 9 | def run 10 | backend = Entities::Backend.find(remote: target_remote, ref: target_backend_ref) 11 | 12 | if backend.nil? 13 | backend = Entities::Backend.create(remote: target_remote, 14 | attrs: create_attrs) 15 | elsif backend == source_backend 16 | message = 'source and destination backends are the same: ' \ 17 | "ID: #{source_backend.id} system_name: #{source_backend.attrs['system_name']}" 18 | warn "\e[1m\e[31mWarning: #{message}\e[0m" 19 | else 20 | backend.update update_attrs 21 | end 22 | 23 | # assign target backend for other tasks to have it available 24 | self.target_backend = backend 25 | 26 | logger.info "source backend ID: #{source_backend.id} system_name: #{source_backend.system_name}" 27 | logger.info "target backend ID: #{target_backend.id} system_name: #{target_backend.system_name}" 28 | report['backend_id'] = target_backend.id 29 | end 30 | 31 | def create_attrs 32 | source_backend.attrs.merge('system_name' => target_backend_ref) 33 | end 34 | 35 | def update_attrs 36 | source_backend.attrs.merge('system_name' => target_backend_ref) 37 | end 38 | 39 | def target_backend_ref 40 | option_target_system_name || source_backend.attrs.fetch('system_name') 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/backend_command/copy_command/delete_mapping_rules_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module BackendCommand 4 | module CopyCommand 5 | class DeleteMappingRulesTask 6 | include Task 7 | 8 | # entrypoint 9 | def run 10 | return unless delete_mapping_rules 11 | 12 | target_backend.mapping_rules.each(&:delete) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/copy_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/copy_command/service_command' 2 | 3 | module ThreeScaleToolbox 4 | module Commands 5 | module CopyCommand 6 | include ThreeScaleToolbox::Command 7 | def self.command 8 | Cri::Command.define do 9 | name 'copy' 10 | usage 'copy [options]' 11 | summary 'copy super command' 12 | description 'Copy 3scale entities between tenants' 13 | 14 | run do |_opts, _args, cmd| 15 | puts cmd.help 16 | end 17 | end 18 | end 19 | add_subcommand(ServiceSubcommand) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/copy_command/service_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module CopyCommand 4 | class ServiceSubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | def self.command 8 | Cri::Command.define do 9 | name 'service' 10 | usage 'service [opts] -s -d ' 11 | summary 'copy service' 12 | description <<-HEREDOC 13 | This command makes a copy of the referenced service. 14 | Target service will be searched by source service system name. System name can be overriden with `--target_system_name` option. 15 | If a service with the selected `system_name` is not found, it will be created. 16 | \n Components of the service being copied: 17 | \nservice settings 18 | \nproxy settings 19 | \npricing rules 20 | \nactivedocs 21 | \nmetrics 22 | \nmethods 23 | \napplication plans 24 | \nmapping rules 25 | HEREDOC 26 | 27 | option :s, :source, '3scale source instance. Url or remote name', argument: :required 28 | option :d, :destination, '3scale target instance. Url or remote name', argument: :required 29 | option :t, 'target_system_name', 'Target system name. Default to source system name', argument: :required 30 | flag :f, :force, 'Overwrites the mapping rules by deleting all rules from target service first' 31 | flag :r, 'rules-only', 'Only mapping rules are copied' 32 | param :source_service 33 | 34 | runner Commands::ServiceCommand::CopySubcommand 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/help_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module HelpCommand 4 | include ThreeScaleToolbox::Command 5 | def self.command 6 | Cri::Command.new_basic_help 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/import_command/import_csv' 2 | require '3scale_toolbox/commands/import_command/openapi' 3 | 4 | module ThreeScaleToolbox 5 | module Commands 6 | module ImportCommand 7 | include ThreeScaleToolbox::Command 8 | def self.command 9 | Cri::Command.define do 10 | name 'import' 11 | usage 'import [options]' 12 | summary 'import super command' 13 | description 'Importing 3scale entities' 14 | 15 | run do |_opts, _args, cmd| 16 | puts cmd.help 17 | end 18 | end 19 | end 20 | add_subcommand(ImportCsvSubcommand) 21 | add_subcommand(OpenAPI::OpenAPISubcommand) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/issuer_type_transformer.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ImportCommand 4 | module OpenAPI 5 | # Helper class to validate values for the oidc-issuer-type argument of the import openapi command 6 | class IssuerTypeTransformer 7 | def call(issuer_type) 8 | raise unless %w[rest keycloak].include?(issuer_type) 9 | 10 | issuer_type 11 | end 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/openapi/create_backend_mapping_rule_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ImportCommand 4 | module OpenAPI 5 | class CreateBackendMappingRulesStep 6 | include Step 7 | 8 | def call 9 | backend.mapping_rules.each(&:delete) 10 | 11 | report['mapping_rules'] = {} 12 | operations.each do |op| 13 | b_m_r = Entities::BackendMappingRule.create(backend: backend, attrs: op.mapping_rule) 14 | report['mapping_rules'][op.friendly_name] = op.mapping_rule 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/openapi/create_backend_method_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ImportCommand 4 | module OpenAPI 5 | class CreateBackendMethodsStep 6 | include Step 7 | 8 | def call 9 | missing_operations.each do |op| 10 | method = Entities::BackendMethod.create(backend: backend, attrs: op.method) 11 | op.set(:metric_id, method.id) 12 | end 13 | 14 | existing_operations.each do |op| 15 | method_attrs = methods_index.fetch(op.method['system_name']).attrs 16 | method = Entities::BackendMethod.new(id: method_attrs.fetch('id'), backend: backend) 17 | method.update(op.method) 18 | op.set(:metric_id, method.id) 19 | end 20 | end 21 | 22 | private 23 | 24 | def methods_index 25 | @methods_index ||= backend.methods.each_with_object({}) do |method, acc| 26 | acc[method.system_name] = method 27 | end 28 | end 29 | 30 | def missing_operations 31 | operations.reject { |op| methods_index.key? op.method['system_name'] } 32 | end 33 | 34 | def existing_operations 35 | operations.select { |op| methods_index.key? op.method['system_name'] } 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/openapi/create_mapping_rule_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ImportCommand 4 | module OpenAPI 5 | class CreateMappingRulesStep 6 | include Step 7 | 8 | def call 9 | report['mapping_rules'] = {} 10 | operations.each do |op| 11 | Entities::MappingRule.create(service: service, 12 | attrs: op.mapping_rule) 13 | logger.info "Created #{op.http_method} #{op.pattern} endpoint" 14 | report['mapping_rules'][op.friendly_name] = op.mapping_rule 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/openapi/create_method_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ImportCommand 4 | module OpenAPI 5 | class CreateMethodsStep 6 | include Step 7 | 8 | def call 9 | missing_operations.each do |op| 10 | method = Entities::Method.create(service: service, attrs: op.method) 11 | op.set(:metric_id, method.id) 12 | end 13 | 14 | existing_operations.each do |op| 15 | method_attrs = methods_index.fetch(op.method['system_name']).attrs 16 | method = Entities::Method.new(id: method_attrs.fetch('id'), service: service) 17 | method.update(op.method) 18 | op.set(:metric_id, method.id) 19 | end 20 | end 21 | 22 | private 23 | 24 | def methods_index 25 | @methods_index ||= service.methods.each_with_object({}) do |method, acc| 26 | acc[method.system_name] = method 27 | end 28 | end 29 | 30 | def missing_operations 31 | operations.reject { |op| methods_index.key? op.method['system_name'] } 32 | end 33 | 34 | def existing_operations 35 | operations.select { |op| methods_index.key? op.method['system_name'] } 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/openapi/import_backend_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ImportCommand 4 | module OpenAPI 5 | class ImportBackendStep 6 | include Step 7 | 8 | def call 9 | verify_params 10 | 11 | tasks = [] 12 | tasks << CreateBackendStep.new(context) 13 | tasks << CreateBackendMethodsStep.new(context) 14 | tasks << CreateBackendMappingRulesStep.new(context) 15 | 16 | # run tasks 17 | tasks.each(&:call) 18 | end 19 | 20 | private 21 | 22 | def verify_params 23 | if private_endpoint.nil? 24 | raise ThreeScaleToolbox::Error, 'private endpoint not specified' 25 | end 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/openapi/import_product_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ImportCommand 4 | module OpenAPI 5 | class ImportProductStep 6 | include Step 7 | 8 | def call 9 | tasks = [] 10 | tasks << CreateServiceStep.new(context) 11 | # other tasks might read proxy settings (CreateActiveDocsStep does) 12 | tasks << UpdateServiceProxyStep.new(context) 13 | tasks << CreateMethodsStep.new(context) 14 | tasks << ThreeScaleToolbox::Commands::ServiceCommand::CopyCommand::DestroyMappingRulesTask.new(context) 15 | tasks << CreateMappingRulesStep.new(context) 16 | tasks << CreateActiveDocsStep.new(context) 17 | tasks << UpdateServiceOidcConfStep.new(context) 18 | tasks << UpdatePoliciesStep.new(context) 19 | 20 | # run tasks 21 | tasks.each(&:call) 22 | 23 | # This should be the last step 24 | ThreeScaleToolbox::Commands::ServiceCommand::CopyCommand::BumpProxyVersionTask.new(service: context[:target]).call 25 | end 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/openapi/mapping_rule.rb: -------------------------------------------------------------------------------- 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 | res = "#{raw_pattern}" 21 | res = "#{res}$" if !operation[:prefix_matching] # apply strict matching 22 | res 23 | end 24 | 25 | def raw_pattern 26 | # According OAS 2.0: path MUST begin with a slash 27 | "#{public_base_path}#{operation[:path]}" 28 | end 29 | 30 | def public_base_path 31 | # remove the last slash of the basePath 32 | operation[:public_base_path].gsub(%r{/$}, '') 33 | end 34 | 35 | def delta 36 | 1 37 | end 38 | 39 | def metric_id 40 | operation[:metric_id] 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/openapi/method.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ImportCommand 4 | module OpenAPI 5 | module Method 6 | def method 7 | { 8 | 'friendly_name' => friendly_name, 9 | 'description' => description, 10 | 'system_name' => system_name 11 | } 12 | end 13 | 14 | def friendly_name 15 | operation[:operation_id] || operation_id 16 | end 17 | 18 | def system_name 19 | friendly_name.downcase.gsub(/[^\w]/, '_') 20 | end 21 | 22 | def operation_id 23 | "#{operation[:verb]}#{operation[:path].gsub(/[^\w]/, '')}" 24 | end 25 | 26 | def description 27 | String(operation[:description]) 28 | end 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/openapi/operation.rb: -------------------------------------------------------------------------------- 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 23 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/import_command/openapi/update_service_oidc_conf_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ImportCommand 4 | module OpenAPI 5 | class UpdateServiceOidcConfStep 6 | include Step 7 | 8 | ## 9 | # Updates OIDC config 10 | def call 11 | # setting required attrs, operation is idempotent 12 | oidc_settings = {} 13 | 14 | add_flow_settings(oidc_settings) 15 | 16 | return unless oidc_settings.size.positive? 17 | 18 | res = service.update_oidc oidc_settings 19 | if (errors = res['errors']) 20 | raise ThreeScaleToolbox::Error, "Service oidc has not been updated. #{errors}" 21 | end 22 | 23 | logger.info 'Service oidc updated' 24 | end 25 | 26 | private 27 | 28 | def add_flow_settings(settings) 29 | # only applies to oauth2 sec type 30 | return if api_spec.security.nil? || api_spec.security[:type] != 'oauth2' 31 | 32 | settings.merge!(api_spec.security[:flows] || {}) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/methods_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/methods_command/create_command' 2 | require '3scale_toolbox/commands/methods_command/list_command' 3 | require '3scale_toolbox/commands/methods_command/apply_command' 4 | require '3scale_toolbox/commands/methods_command/delete_command' 5 | 6 | module ThreeScaleToolbox 7 | module Commands 8 | module MethodsCommand 9 | include ThreeScaleToolbox::Command 10 | def self.command 11 | Cri::Command.define do 12 | name 'method' 13 | usage 'method [options]' 14 | summary 'method super command' 15 | description 'Method commands' 16 | 17 | run do |_opts, _args, cmd| 18 | puts cmd.help 19 | end 20 | end 21 | end 22 | add_subcommand(Create::CreateSubcommand) 23 | add_subcommand(List::ListSubcommand) 24 | add_subcommand(Apply::ApplySubcommand) 25 | add_subcommand(Delete::DeleteSubcommand) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/methods_command/list_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module MethodsCommand 4 | module List 5 | class ListSubcommand < Cri::CommandRunner 6 | include ThreeScaleToolbox::Command 7 | 8 | FIELDS_TO_SHOW = %w[id friendly_name system_name description].freeze 9 | 10 | def self.command 11 | Cri::Command.define do 12 | name 'list' 13 | usage 'list [opts] ' 14 | summary 'list methods' 15 | description 'List methods' 16 | 17 | ThreeScaleToolbox::CLI.output_flag(self) 18 | param :remote 19 | param :service_ref 20 | 21 | runner ListSubcommand 22 | end 23 | end 24 | 25 | def run 26 | printer.print_collection service.methods.map(&:attrs) 27 | end 28 | 29 | private 30 | 31 | def service 32 | @service ||= find_service 33 | end 34 | 35 | def find_service 36 | Entities::Service.find(remote: remote, 37 | ref: service_ref).tap do |svc| 38 | raise ThreeScaleToolbox::Error, "Service #{service_ref} does not exist" if svc.nil? 39 | end 40 | end 41 | 42 | def remote 43 | @remote ||= threescale_client(arguments[:remote]) 44 | end 45 | 46 | def service_ref 47 | arguments[:service_ref] 48 | end 49 | 50 | def printer 51 | # keep backwards compatibility 52 | options.fetch(:output, CLI::CustomTablePrinter.new(FIELDS_TO_SHOW)) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/metrics_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/metrics_command/create_command' 2 | require '3scale_toolbox/commands/metrics_command/list_command' 3 | require '3scale_toolbox/commands/metrics_command/apply_command' 4 | require '3scale_toolbox/commands/metrics_command/delete_command' 5 | 6 | module ThreeScaleToolbox 7 | module Commands 8 | module MetricsCommand 9 | include ThreeScaleToolbox::Command 10 | def self.command 11 | Cri::Command.define do 12 | name 'metric' 13 | usage 'metric [options]' 14 | summary 'metric super command' 15 | description 'Metric commands' 16 | 17 | run do |_opts, _args, cmd| 18 | puts cmd.help 19 | end 20 | end 21 | end 22 | add_subcommand(Create::CreateSubcommand) 23 | add_subcommand(List::ListSubcommand) 24 | add_subcommand(Apply::ApplySubcommand) 25 | add_subcommand(Delete::DeleteSubcommand) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/metrics_command/delete_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module MetricsCommand 4 | module Delete 5 | class DeleteSubcommand < Cri::CommandRunner 6 | include ThreeScaleToolbox::Command 7 | 8 | def self.command 9 | Cri::Command.define do 10 | name 'delete' 11 | usage 'delete [opts] ' 12 | summary 'delete metric' 13 | description 'Delete metric' 14 | 15 | param :remote 16 | param :service_ref 17 | param :metric_ref 18 | 19 | runner DeleteSubcommand 20 | end 21 | end 22 | 23 | def run 24 | metric.delete 25 | puts "Metric id: #{metric.id} deleted" 26 | end 27 | 28 | private 29 | 30 | def service 31 | @service ||= find_service 32 | end 33 | 34 | def metric 35 | @metric ||= find_metric 36 | end 37 | 38 | def find_service 39 | Entities::Service.find(remote: remote, 40 | ref: service_ref).tap do |svc| 41 | raise ThreeScaleToolbox::Error, "Service #{service_ref} does not exist" if svc.nil? 42 | end 43 | end 44 | 45 | def find_metric 46 | Entities::Metric.find(service: service, ref: metric_ref).tap do |p| 47 | raise ThreeScaleToolbox::Error, "Metric #{metric_ref} does not exist" if p.nil? 48 | end 49 | end 50 | 51 | def remote 52 | @remote ||= threescale_client(arguments[:remote]) 53 | end 54 | 55 | def service_ref 56 | arguments[:service_ref] 57 | end 58 | 59 | def metric_ref 60 | arguments[:metric_ref] 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/metrics_command/list_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module MetricsCommand 4 | module List 5 | class ListSubcommand < Cri::CommandRunner 6 | include ThreeScaleToolbox::Command 7 | 8 | FIELDS_TO_SHOW = %w[id friendly_name system_name unit description].freeze 9 | 10 | def self.command 11 | Cri::Command.define do 12 | name 'list' 13 | usage 'list [opts] ' 14 | summary 'list metrics' 15 | description 'List metrics' 16 | 17 | ThreeScaleToolbox::CLI.output_flag(self) 18 | param :remote 19 | param :service_ref 20 | 21 | runner ListSubcommand 22 | end 23 | end 24 | 25 | def run 26 | printer.print_collection service.metrics.map(&:attrs) 27 | end 28 | 29 | private 30 | 31 | def service 32 | @service ||= find_service 33 | end 34 | 35 | def find_service 36 | Entities::Service.find(remote: remote, 37 | ref: service_ref).tap do |svc| 38 | raise ThreeScaleToolbox::Error, "Service #{service_ref} does not exist" if svc.nil? 39 | end 40 | end 41 | 42 | def remote 43 | @remote ||= threescale_client(arguments[:remote]) 44 | end 45 | 46 | def service_ref 47 | arguments[:service_ref] 48 | end 49 | 50 | def printer 51 | # keep backwards compatibility 52 | options.fetch(:output, CLI::CustomTablePrinter.new(FIELDS_TO_SHOW)) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/plans_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/plans_command/export_command' 2 | require '3scale_toolbox/commands/plans_command/import_command' 3 | require '3scale_toolbox/commands/plans_command/create_command' 4 | require '3scale_toolbox/commands/plans_command/apply_command' 5 | require '3scale_toolbox/commands/plans_command/list_command' 6 | require '3scale_toolbox/commands/plans_command/show_command' 7 | require '3scale_toolbox/commands/plans_command/delete_command' 8 | 9 | module ThreeScaleToolbox 10 | module Commands 11 | module PlansCommand 12 | include ThreeScaleToolbox::Command 13 | def self.command 14 | Cri::Command.define do 15 | name 'application-plan' 16 | usage 'application-plan [options]' 17 | summary 'application-plan super command' 18 | description 'Application plan commands' 19 | 20 | run do |_opts, _args, cmd| 21 | puts cmd.help 22 | end 23 | end 24 | end 25 | add_subcommand(Export::ExportSubcommand) 26 | add_subcommand(Import::ImportSubcommand) 27 | add_subcommand(Create::CreateSubcommand) 28 | add_subcommand(Apply::ApplySubcommand) 29 | add_subcommand(List::ListSubcommand) 30 | add_subcommand(Show::ShowSubcommand) 31 | add_subcommand(Delete::DeleteSubcommand) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/plans_command/import/create_or_update_app_plan_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module PlansCommand 4 | module Import 5 | class CreateOrUpdateAppPlanStep 6 | include Step 7 | ## 8 | # Creates if it does not exist, updates otherwise 9 | def call 10 | plan_obj = Entities::ApplicationPlan.find(service: service, ref: plan_system_name) 11 | if plan_obj.nil? 12 | plan_obj = Entities::ApplicationPlan.create(service: service, 13 | plan_attrs: create_plan_attrs) 14 | puts "Application plan created: #{plan_obj.id}" 15 | else 16 | res = plan_obj.update(update_plan_attrs) 17 | if (errors = res['errors']) 18 | raise ThreeScaleToolbox::Error, "Could not update application plan #{plan_system_name}. Errors: #{errors}" 19 | end 20 | 21 | puts "Application plan updated: #{plan_obj.id}" 22 | end 23 | end 24 | 25 | private 26 | 27 | def create_plan_attrs 28 | resource_plan.merge('system_name' => plan_system_name) 29 | end 30 | 31 | def update_plan_attrs 32 | resource_plan.reject { |key, _| %w[system_name].include? key } 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/plans_command/import/import_backend_metrics_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module PlansCommand 4 | module Import 5 | class ImportBackendMetricsStep 6 | include Step 7 | ## 8 | # Writes Plan metrics and methods 9 | def call 10 | resource_backend_metrics.each(&method(:create_metric)) 11 | resource_backend_methods.each(&method(:create_method)) 12 | end 13 | 14 | private 15 | 16 | def create_metric(metric_attrs) 17 | backend = find_backend(metric_attrs.fetch('backend_system_name')) 18 | 19 | unless backend.metrics.any? { |m| m.system_name == metric_attrs.fetch('system_name') } 20 | Entities::BackendMetric.create(backend: backend, attrs: metric_attrs) 21 | puts "Created backend metric: #{metric_attrs.fetch('system_name')}; backend: #{backend.system_name}" 22 | end 23 | end 24 | 25 | def create_method(method_attrs) 26 | backend = find_backend(method_attrs.fetch('backend_system_name')) 27 | 28 | unless backend.methods.any? { |m| m.system_name == method_attrs.fetch('system_name') } 29 | Entities::BackendMethod.create(backend: backend, attrs: method_attrs) 30 | puts "Created backend method: #{method_attrs.fetch('system_name')}; backend: #{backend.system_name}" 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/plans_command/import/import_plan_features_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module PlansCommand 4 | module Import 5 | class ImportPlanFeaturesStep 6 | include Step 7 | ## 8 | # Writes Plan features 9 | def call 10 | missing_features.each do |feature| 11 | create_plan_feature(feature) 12 | puts "Created plan feature: #{feature}" 13 | end 14 | end 15 | 16 | private 17 | 18 | def missing_features 19 | ThreeScaleToolbox::Helper.array_difference(resource_features, plan.features) do |a, b| 20 | ThreeScaleToolbox::Helper.compare_hashes(a, b, ['system_name']) 21 | end 22 | end 23 | 24 | def create_plan_feature(feature_attrs) 25 | feature = find_feature_by_system_name(feature_attrs['system_name']) || create_service_feature(feature_attrs) 26 | 27 | plan.create_feature(feature['id']).tap do |resp| 28 | if (errors = resp['errors']) 29 | raise ThreeScaleToolbox::Error, "Plan feature has not been created. #{errors}" 30 | end 31 | end 32 | end 33 | 34 | def create_service_feature(feature_attrs) 35 | service.create_feature(feature_attrs).tap do |resp| 36 | if (errors = resp['errors']) 37 | raise ThreeScaleToolbox::Error, "Service feature has not been created. #{errors}" 38 | end 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/plans_command/import/import_plan_limits_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module PlansCommand 4 | module Import 5 | class ImportLimitsStep 6 | include Step 7 | ## 8 | # Writes Plan limits 9 | def call 10 | # SET semantics 11 | # First, delete existing limits 12 | # Second, add new limits 13 | plan.limits.each do |limit| 14 | limit.delete() 15 | puts "Deleted existing plan limit: [metric: #{limit.metric_id}, #{limit.attrs}]" 16 | end 17 | 18 | resource_limits_processed.each do |limit_attrs| 19 | metric_id = limit_attrs.delete('metric_id') 20 | plan.create_limit(metric_id, limit_attrs) 21 | puts "Created plan limit: [metric: #{metric_id}, #{limit_attrs}]" 22 | end 23 | end 24 | 25 | private 26 | 27 | def resource_limits_processed 28 | resource_limits.map do |limit| 29 | metric_system_name = limit.delete('metric_system_name') 30 | backend_system_name = limit.delete('metric_backend_system_name') 31 | metric_owner = if backend_system_name.nil? 32 | service 33 | else 34 | find_backend(backend_system_name) 35 | end 36 | metric = metric_owner.find_metric_or_method(metric_system_name) 37 | # this ImportMetricLimitsStep step is assuming all metrics/methods have been created 38 | # in previous step, so finding metric should always succeed. 39 | raise ThreeScaleToolbox::Error, "metric [#{metric_system_name}, #{backend_system_name}] not found" if metric.nil? 40 | 41 | limit.merge('metric_id' => metric.id) 42 | end 43 | end 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/plans_command/import/import_plan_metrics_step.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module PlansCommand 4 | module Import 5 | class ImportMetricsStep 6 | include Step 7 | ## 8 | # Writes Plan metrics and methods 9 | def call 10 | missing_metrics.each(&method(:create_metric)) 11 | missing_methods.each(&method(:create_method)) 12 | end 13 | 14 | private 15 | 16 | def missing_metrics 17 | ThreeScaleToolbox::Helper.array_difference(resource_product_metrics, service.metrics) do |a, b| 18 | a['system_name'] == b.system_name 19 | end 20 | end 21 | 22 | def missing_methods 23 | ThreeScaleToolbox::Helper.array_difference(resource_product_methods, service.methods) do |a, b| 24 | a['system_name'] == b.system_name 25 | end 26 | end 27 | 28 | def create_metric(metric_attrs) 29 | metric = ThreeScaleToolbox::Entities::Metric.create(service: service, attrs: metric_attrs) 30 | puts "Created metric: #{metric.attrs['system_name']}" 31 | end 32 | 33 | def create_method(method_attrs) 34 | method = ThreeScaleToolbox::Entities::Method.create(service: service, attrs: method_attrs) 35 | puts "Created method: #{method.attrs['system_name']}" 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/plans_command/list_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module PlansCommand 4 | module List 5 | class ListSubcommand < Cri::CommandRunner 6 | include ThreeScaleToolbox::Command 7 | 8 | FIELDS = %w[id name system_name].freeze 9 | 10 | def self.command 11 | Cri::Command.define do 12 | name 'list' 13 | usage 'list [opts] ' 14 | summary 'list application plans' 15 | description 'List application plans' 16 | 17 | ThreeScaleToolbox::CLI.output_flag(self) 18 | param :remote 19 | param :service_ref 20 | 21 | runner ListSubcommand 22 | end 23 | end 24 | 25 | def run 26 | printer.print_collection service.plans.map(&:attrs) 27 | end 28 | 29 | private 30 | 31 | def service 32 | @service ||= find_service 33 | end 34 | 35 | def find_service 36 | Entities::Service.find(remote: remote, 37 | ref: service_ref).tap do |svc| 38 | raise ThreeScaleToolbox::Error, "Service #{service_ref} does not exist" if svc.nil? 39 | end 40 | end 41 | 42 | def remote 43 | @remote ||= threescale_client(arguments[:remote]) 44 | end 45 | 46 | def service_ref 47 | arguments[:service_ref] 48 | end 49 | 50 | def printer 51 | # keep backwards compatibility 52 | options.fetch(:output, CLI::CustomTablePrinter.new(FIELDS)) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/policies_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/policies_command/export_command' 2 | require '3scale_toolbox/commands/policies_command/import_command' 3 | 4 | module ThreeScaleToolbox 5 | module Commands 6 | module PoliciesCommand 7 | include ThreeScaleToolbox::Command 8 | def self.command 9 | Cri::Command.define do 10 | name 'policies' 11 | usage 'policies [options]' 12 | summary 'policies super command' 13 | description 'Policies commands' 14 | 15 | run do |_opts, _args, cmd| 16 | puts cmd.help 17 | end 18 | end 19 | end 20 | add_subcommand(ExportSubcommand) 21 | add_subcommand(ImportSubcommand) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/policy_registry_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/policy_registry_command/copy_command' 2 | 3 | module ThreeScaleToolbox 4 | module Commands 5 | module PolicyRegistryCommand 6 | include ThreeScaleToolbox::Command 7 | def self.command 8 | Cri::Command.define do 9 | name 'policy-registry' 10 | usage 'policy-registry [options]' 11 | summary 'policy-registry super command' 12 | description 'Pôlicy Registry commands' 13 | 14 | run do |_opts, _args, cmd| 15 | puts cmd.help 16 | end 17 | end 18 | end 19 | add_subcommand(Copy::CopySubcommand) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/product_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/product_command/copy_command' 2 | require '3scale_toolbox/commands/product_command/export_command' 3 | require '3scale_toolbox/commands/product_command/import_command' 4 | 5 | module ThreeScaleToolbox 6 | module Commands 7 | module ProductCommand 8 | include ThreeScaleToolbox::Command 9 | def self.command 10 | Cri::Command.define do 11 | name 'product' 12 | usage 'product [options]' 13 | summary 'product super command' 14 | description 'Product commands' 15 | 16 | run do |_opts, _args, cmd| 17 | puts cmd.help 18 | end 19 | end 20 | end 21 | add_subcommand(CopySubcommand) 22 | add_subcommand(ExportSubcommand) 23 | add_subcommand(ImportSubcommand) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/product_command/copy_command/delete_target_backend_usages_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ProductCommand 4 | module CopyCommand 5 | class DeleteExistingTargetBackendUsagesTask 6 | attr_reader :context 7 | 8 | def initialize(context) 9 | @context = context 10 | end 11 | 12 | # entrypoint 13 | def call 14 | conflicting_target_backend_usage_list.each(&:delete) 15 | end 16 | 17 | private 18 | 19 | # List of target backend usage items that match source backend usage paths 20 | def conflicting_target_backend_usage_list 21 | # Compute array intersection 22 | target_backend_usage_list.select do |target_usage| 23 | source_backend_usage_list.find do |source_usage| 24 | target_usage.path == source_usage.path 25 | end 26 | end 27 | end 28 | 29 | def source_backend_usage_list 30 | @source_backend_usage_list ||= source.backend_usage_list 31 | end 32 | 33 | def target_backend_usage_list 34 | @target_backend_usage_list ||= target.backend_usage_list 35 | end 36 | 37 | def target 38 | context[:target] 39 | end 40 | 41 | def source 42 | context[:source] 43 | end 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/proxy_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/proxy_command/update_command' 2 | require '3scale_toolbox/commands/proxy_command/deploy_command' 3 | require '3scale_toolbox/commands/proxy_command/show_command' 4 | 5 | module ThreeScaleToolbox 6 | module Commands 7 | module ProxyCommand 8 | include ThreeScaleToolbox::Command 9 | 10 | def self.command 11 | Cri::Command.define do 12 | name 'proxy' 13 | usage 'proxy [options]' 14 | summary 'proxy super command' 15 | description 'APIcast configuration commands' 16 | 17 | run do |_opts, _args, cmd| 18 | puts cmd.help 19 | end 20 | end 21 | end 22 | 23 | add_subcommand(UpdateSubcommand) 24 | add_subcommand(DeploySubcommand) 25 | add_subcommand(ShowSubcommand) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/proxy_command/deploy_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ProxyCommand 4 | class DeploySubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | def self.command 8 | Cri::Command.define do 9 | name 'deploy' 10 | usage 'deploy ' 11 | summary 'Promotes the APIcast configuration to the Staging Environment' 12 | description 'Promotes the APIcast configuration to the Staging Environment (Production Environment in case of Service Mesh).' 13 | 14 | param :remote 15 | param :service_ref 16 | 17 | ThreeScaleToolbox::CLI.output_flag(self) 18 | 19 | runner DeploySubcommand 20 | end 21 | end 22 | 23 | def run 24 | printer.print_record service.proxy_deploy 25 | end 26 | 27 | private 28 | 29 | def remote 30 | @remote ||= threescale_client(arguments[:remote]) 31 | end 32 | 33 | def service_ref 34 | arguments[:service_ref] 35 | end 36 | 37 | def find_service 38 | Entities::Service.find(remote: remote, 39 | ref: service_ref).tap do |svc| 40 | raise ThreeScaleToolbox::Error, "Service #{service_ref} does not exist" if svc.nil? 41 | end 42 | end 43 | 44 | def service 45 | @service ||= find_service 46 | end 47 | 48 | def printer 49 | options.fetch(:output, CLI::JsonPrinter.new) 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/proxy_command/show_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ProxyCommand 4 | class ShowSubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | def self.command 8 | Cri::Command.define do 9 | name 'show' 10 | usage 'show ' 11 | summary 'Fetch (undeployed) APIcast configuration' 12 | description 'Fetch (undeployed) APIcast configuration' 13 | 14 | param :remote 15 | param :service_ref 16 | 17 | ThreeScaleToolbox::CLI.output_flag(self) 18 | 19 | runner ShowSubcommand 20 | end 21 | end 22 | 23 | def run 24 | printer.print_record service.proxy 25 | end 26 | 27 | private 28 | 29 | def remote 30 | @remote ||= threescale_client(arguments[:remote]) 31 | end 32 | 33 | def service_ref 34 | arguments[:service_ref] 35 | end 36 | 37 | def find_service 38 | Entities::Service.find(remote: remote, 39 | ref: service_ref).tap do |svc| 40 | raise ThreeScaleToolbox::Error, "Service #{service_ref} does not exist" if svc.nil? 41 | end 42 | end 43 | 44 | def service 45 | @service ||= find_service 46 | end 47 | 48 | def printer 49 | options.fetch(:output, CLI::JsonPrinter.new) 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/proxy_config_command.rb: -------------------------------------------------------------------------------- 1 | require 'cri' 2 | require '3scale_toolbox/base_command' 3 | require '3scale_toolbox/commands/proxy_config_command/helper' 4 | require '3scale_toolbox/commands/proxy_config_command/list_command' 5 | require '3scale_toolbox/commands/proxy_config_command/show_command' 6 | require '3scale_toolbox/commands/proxy_config_command/promote_command' 7 | require '3scale_toolbox/commands/proxy_config_command/export_command' 8 | require '3scale_toolbox/commands/proxy_config_command/deploy_command' 9 | 10 | module ThreeScaleToolbox 11 | module Commands 12 | module ProxyConfigCommand 13 | include ThreeScaleToolbox::Command 14 | 15 | def self.command 16 | Cri::Command.define do 17 | name 'proxy-config' 18 | usage 'proxy-config [options]' 19 | summary 'proxy-config super command' 20 | description 'Manage your Proxy Configurations' 21 | 22 | run do |_opts, _args, cmd| 23 | puts cmd.help 24 | end 25 | end 26 | end 27 | 28 | add_subcommand(List::ListSubcommand) 29 | add_subcommand(Show::ShowSubcommand) 30 | add_subcommand(Promote::PromoteSubcommand) 31 | add_subcommand(Export::ExportSubcommand) 32 | add_subcommand(DeploySubcommand) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/proxy_config_command/deploy_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ProxyConfigCommand 4 | class DeploySubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | def self.command 8 | Cri::Command.define do 9 | name 'deploy' 10 | usage 'deploy ' 11 | summary '[DEPRECATED] Promotes the APIcast configuration to the Staging Environment' 12 | description '[DEPRECATED] Promotes the APIcast configuration to the Staging Environment (Production Environment in case of Service Mesh).' 13 | 14 | param :remote 15 | param :service_ref 16 | 17 | ThreeScaleToolbox::CLI.output_flag(self) 18 | 19 | runner DeploySubcommand 20 | end 21 | end 22 | 23 | def run 24 | warn "\e[1m\e[31mThis command has been deprecated. Use '3scale proxy deploy' instead\e[0m" 25 | printer.print_record service.proxy_deploy 26 | end 27 | 28 | private 29 | 30 | def remote 31 | @remote ||= threescale_client(arguments[:remote]) 32 | end 33 | 34 | def service_ref 35 | arguments[:service_ref] 36 | end 37 | 38 | def find_service 39 | Entities::Service.find(remote: remote, 40 | ref: service_ref).tap do |svc| 41 | raise ThreeScaleToolbox::Error, "Service #{service_ref} does not exist" if svc.nil? 42 | end 43 | end 44 | 45 | def service 46 | @service ||= find_service 47 | end 48 | 49 | def printer 50 | options.fetch(:output, CLI::JsonPrinter.new) 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/proxy_config_command/helper.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ProxyConfigCommand 4 | class EnvironmentTransformer 5 | def call(param_str) 6 | raise ArgumentError unless param_str.is_a?(String) 7 | 8 | raise ArgumentError unless %w[production sandbox].include? param_str 9 | 10 | param_str 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/proxy_config_command/list_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ProxyConfigCommand 4 | module List 5 | class ListSubcommand < Cri::CommandRunner 6 | include ThreeScaleToolbox::Command 7 | 8 | FIELDS = %w[id version environment] 9 | 10 | def self.command 11 | Cri::Command.define do 12 | name 'list' 13 | usage 'list ' 14 | summary 'List Proxy Configurations' 15 | description 'List all defined Proxy Configurations' 16 | 17 | ThreeScaleToolbox::CLI.output_flag(self) 18 | param :remote 19 | param :service_ref 20 | param :environment 21 | 22 | runner ListSubcommand 23 | end 24 | end 25 | 26 | def run 27 | printer.print_collection service.proxy_configs(proxy_config_environment).map(&:attrs) 28 | end 29 | 30 | private 31 | 32 | def remote 33 | @remote ||= threescale_client(arguments[:remote]) 34 | end 35 | 36 | def proxy_config_environment 37 | arguments[:environment] 38 | end 39 | 40 | def service_ref 41 | arguments[:service_ref] 42 | end 43 | 44 | def find_service 45 | Entities::Service.find(remote: remote, ref: service_ref).tap do |svc| 46 | raise ThreeScaleToolbox::Error, "Service #{service_ref} does not exist" if svc.nil? 47 | end 48 | end 49 | 50 | def service 51 | @service ||= find_service 52 | end 53 | 54 | def printer 55 | # keep backwards compatibility 56 | options.fetch(:output, CLI::CustomTablePrinter.new(FIELDS)) 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/remote_command.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/commands/remote_command/remote_add' 2 | require '3scale_toolbox/commands/remote_command/remote_remove' 3 | require '3scale_toolbox/commands/remote_command/remote_rename' 4 | require '3scale_toolbox/commands/remote_command/remote_list' 5 | 6 | module ThreeScaleToolbox 7 | module Commands 8 | module RemoteCommand 9 | class RemoteCommand < Cri::CommandRunner 10 | include ThreeScaleToolbox::Command 11 | 12 | def self.command 13 | Cri::Command.define do 14 | name 'remote' 15 | usage 'remote [options]' 16 | summary 'remotes super command' 17 | description 'Manage your remotes' 18 | runner RemoteCommand 19 | end 20 | end 21 | 22 | def run 23 | puts command.help 24 | end 25 | 26 | add_subcommand(RemoteAddSubcommand) 27 | add_subcommand(RemoteRemoveSubcommand) 28 | add_subcommand(RemoteRenameSubcommand) 29 | add_subcommand(RemoteListSubcommand) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/remote_command/remote_add.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module RemoteCommand 4 | class RemoteAddSubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | def self.command 8 | Cri::Command.define do 9 | name 'add' 10 | usage 'add ' 11 | summary 'remote add' 12 | description 'Add new remote to the list' 13 | param :remote_name 14 | param :remote_url 15 | runner RemoteAddSubcommand 16 | end 17 | end 18 | 19 | def run 20 | # 'arguments' cannot be converted to Hash 21 | add_remote arguments[:remote_name], arguments[:remote_url] 22 | end 23 | 24 | private 25 | 26 | def validate_remote_name(name) 27 | raise ThreeScaleToolbox::Error, 'remote name already exists.' if remotes.all.key?(name) 28 | end 29 | 30 | def validate_remote(remote_url_str) 31 | uri_obj = ThreeScaleToolbox::Helper.parse_uri(remote_url_str) 32 | raise ThreeScaleToolbox::InvalidUrlError, "invalid url: #{remote_url_str}" unless uri_obj.kind_of?(URI::HTTP) 33 | 34 | threescale_client(remote_url_str).list_accounts 35 | rescue ThreeScale::API::HttpClient::ForbiddenError 36 | raise ThreeScaleToolbox::Error, 'remote not valid' 37 | end 38 | 39 | def add_remote(remote_name, remote_url) 40 | validate_remote_name remote_name 41 | validate_remote remote_url 42 | remotes.add_uri(remote_name, remote_url) 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/remote_command/remote_list.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module RemoteCommand 4 | class RemoteListSubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | def self.command 8 | Cri::Command.define do 9 | name 'list' 10 | usage 'list' 11 | summary 'remote list' 12 | description 'List all defined remotes' 13 | runner RemoteListSubcommand 14 | end 15 | end 16 | 17 | def run 18 | if remotes.all.empty? 19 | puts 'Empty remote list.' 20 | else 21 | remotes.all.each do |name, remote| 22 | puts "#{name} #{remote[:endpoint]} #{remote[:authentication]}" 23 | end 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/remote_command/remote_remove.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module RemoteCommand 4 | class RemoteRemoveSubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | def self.command 8 | Cri::Command.define do 9 | name 'remove' 10 | usage 'remove ' 11 | summary 'remote remove' 12 | description 'Remove remote from list' 13 | param :remote_name 14 | runner RemoteRemoveSubcommand 15 | end 16 | end 17 | 18 | def run 19 | remotes.delete(arguments[:remote_name]) do |el| 20 | raise ThreeScaleToolbox::Error, "could not remove remote '#{el}'" 21 | end 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/remote_command/remote_rename.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module RemoteCommand 4 | class RemoteRenameSubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | def self.command 8 | Cri::Command.define do 9 | name 'rename' 10 | usage 'rename ' 11 | summary 'remote rename' 12 | description 'Rename remote name' 13 | param :remote_old_name 14 | param :remote_new_name 15 | runner RemoteRenameSubcommand 16 | end 17 | end 18 | 19 | def run 20 | # 'arguments' cannot be converted to Hash 21 | rename_remote arguments[:remote_old_name], arguments[:remote_new_name] 22 | end 23 | 24 | private 25 | 26 | def validate_remote_old_name(name) 27 | raise ThreeScaleToolbox::Error, "Could not rename, old name '#{name}' does not exist." unless remotes.all.key?(name) 28 | end 29 | 30 | def validate_remote_new_name(name) 31 | raise ThreeScaleToolbox::Error, "Could not rename, new name '#{name}' already exists." if remotes.all.key?(name) 32 | end 33 | 34 | def rename_remote(remote_old_name, remote_new_name) 35 | validate_remote_old_name remote_old_name 36 | validate_remote_new_name remote_new_name 37 | remotes.add(remote_new_name, remotes.delete(remote_old_name)) 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command.rb: -------------------------------------------------------------------------------- 1 | require 'cri' 2 | require '3scale_toolbox/base_command' 3 | require '3scale_toolbox/commands/service_command/list_command' 4 | require '3scale_toolbox/commands/service_command/show_command' 5 | require '3scale_toolbox/commands/service_command/delete_command' 6 | require '3scale_toolbox/commands/service_command/create_command' 7 | require '3scale_toolbox/commands/service_command/apply_command' 8 | require '3scale_toolbox/commands/service_command/copy_command' 9 | 10 | module ThreeScaleToolbox 11 | module Commands 12 | module ServiceCommand 13 | include ThreeScaleToolbox::Command 14 | 15 | def self.command 16 | Cri::Command.define do 17 | name 'service' 18 | usage 'service [options]' 19 | summary 'services super command' 20 | description 'Manage your services' 21 | 22 | run do |_opts, _args, cmd| 23 | puts cmd.help 24 | end 25 | end 26 | end 27 | 28 | add_subcommand(ListSubcommand) 29 | add_subcommand(ShowSubcommand) 30 | add_subcommand(DeleteSubcommand) 31 | add_subcommand(CreateSubcommand) 32 | add_subcommand(ApplySubcommand) 33 | add_subcommand(CopySubcommand) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/bump_proxy_version_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class BumpProxyVersionTask 6 | attr_reader :service 7 | 8 | def initialize(service:) 9 | @service = service 10 | end 11 | 12 | ## 13 | # bumps proxy config version to propagate proxy settings updates 14 | def call 15 | # Proxy update is the mechanism to increase version of the proxy, 16 | # Hence propagating (mapping rules, poicies, oidc, auth) update to 17 | # latest proxy config, making available to gateway. 18 | 19 | # Currently it is done always because mapping rules, at least, are always created 20 | # So they need to be propagated 21 | proxy_settings = { 22 | # Adding harmless attribute to avoid empty body 23 | # update_proxy cannot be done with empty body 24 | # and must be done to increase proxy version 25 | # If proxy settings have not been changed since last update, 26 | # this request will not have effect and proxy config version will not be bumped. 27 | service_id: service.id 28 | } 29 | 30 | service.update_proxy proxy_settings 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/copy_app_plans_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class CopyApplicationPlansTask 6 | include Task 7 | 8 | def call 9 | missing_regular_plans.each do |plan| 10 | plan_attrs = plan.attrs.clone 11 | plan_attrs.delete('links') 12 | plan_attrs.delete('default') # TODO: handle default plan 13 | ThreeScaleToolbox::Entities::ApplicationPlan.create(service: target, plan_attrs: plan_attrs) 14 | end 15 | 16 | logger.info "target service missing #{missing_regular_plans.size} application plans" 17 | report['missing_application_plans_created'] = missing_regular_plans.size 18 | end 19 | 20 | private 21 | 22 | def missing_regular_plans 23 | missing_plans.reject(&:custom) 24 | end 25 | 26 | def missing_plans 27 | @missing_plans ||= ThreeScaleToolbox::Helper.array_difference(source.plans, target.plans) do |src, target| 28 | src.system_name == target.system_name 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/copy_limits_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class CopyLimitsTask 6 | include Task 7 | 8 | def call 9 | plan_mapping = Helper.application_plan_mapping(source.plans, target.plans) 10 | plan_mapping.each do |source_plan, target_plan| 11 | missing_limits = compute_missing_limits(source_plan.limits, target_plan.limits) 12 | missing_limits.each do |limit| 13 | target_plan.create_limit(metrics_map.fetch(limit.metric_id), limit.attrs) 14 | end 15 | logger.info "Missing #{missing_limits.size} plan limits from target application plan " \ 16 | "#{target_plan.id}. Source plan #{source_plan.id}" 17 | 18 | plans_report[target_plan.system_name] = {'application_plan_id' => target_plan.id} unless plans_report.has_key? target_plan.system_name 19 | plans_report[target_plan.system_name].merge!({'missing_limits_created' => missing_limits.size}) 20 | end 21 | end 22 | 23 | private 24 | 25 | def metrics_map 26 | @metrics_map ||= source.metrics_mapping(target) 27 | end 28 | 29 | def compute_missing_limits(source_limits, target_limits) 30 | ThreeScaleToolbox::Helper.array_difference(source_limits, target_limits) do |limit, target_limit| 31 | limit.period == target_limit.period && metrics_map.fetch(limit.metric_id) == target_limit.metric_id 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/copy_mapping_rules_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class CopyMappingRulesTask 6 | include Task 7 | 8 | def call 9 | missing_rules.each do |mapping_rule| 10 | mr_attrs = mapping_rule.attrs.merge('metric_id' => metrics_map.fetch(mapping_rule.metric_id)) 11 | Entities::MappingRule.create(service: target, attrs: mr_attrs) 12 | end 13 | logger.info "created #{missing_rules.size} mapping rules" 14 | report['missing_mapping_rules_created'] = missing_rules.size 15 | end 16 | 17 | private 18 | 19 | def metrics_map 20 | @metrics_map ||= source.metrics_mapping(target) 21 | end 22 | 23 | def missing_rules 24 | @missing_rules ||= ThreeScaleToolbox::Helper.array_difference(source.mapping_rules, target.mapping_rules) do |source_rule, target_rule| 25 | source_rule.pattern == target_rule.pattern && 26 | source_rule.http_method == target_rule.http_method && 27 | source_rule.delta == target_rule.delta && 28 | metrics_map.fetch(source_rule.metric_id) == target_rule.metric_id 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/copy_methods_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class CopyMethodsTask 6 | include Task 7 | 8 | def call 9 | logger.info "original service hits metric #{source.hits.id} has #{source.methods.size} methods" 10 | logger.info "target service hits metric #{target.hits.id} has #{target.methods.size} methods" 11 | missing_methods.each(&method(:create_method)) 12 | logger.info "created #{missing_methods.size} missing methods on target service" 13 | report['missing_methods_created'] = missing_methods.size 14 | end 15 | 16 | private 17 | 18 | def create_method(method) 19 | new_method = method.attrs.reject { |key, _| %w[id links].include? key } 20 | Entities::Method.create(service: target, attrs: new_method) 21 | rescue ThreeScaleToolbox::ThreeScaleApiError => e 22 | raise e unless ThreeScaleToolbox::Helper.system_name_already_taken_error?(e.apierrors) 23 | 24 | warn "[WARN] method #{method.system_name} not created. " \ 25 | 'Metric with the same system_name exists.' 26 | end 27 | 28 | def missing_methods 29 | @missing_methods ||= ThreeScaleToolbox::Helper.array_difference(source.methods, target.methods) do |method, target| 30 | method.system_name == target.system_name 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/copy_metrics_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class CopyMetricsTask 6 | include Task 7 | 8 | def call 9 | logger.info "original service has #{source.metrics.size} metrics" 10 | logger.info "target service has #{target.metrics.size} metrics" 11 | missing_metrics.each(&method(:create_metric)) 12 | logger.info "created #{missing_metrics.size} metrics on the target service" 13 | report['missing_metrics_created'] = missing_metrics.size 14 | end 15 | 16 | private 17 | 18 | def create_metric(metric) 19 | new_metric = metric.attrs.reject { |key, _| %w[id links].include? key } 20 | Entities::Metric.create(service: target, attrs: new_metric) 21 | rescue ThreeScaleToolbox::ThreeScaleApiError => e 22 | raise e unless ThreeScaleToolbox::Helper.system_name_already_taken_error?(e.apierrors) 23 | 24 | warn "[WARN] metric #{metric.system_name} not created. " \ 25 | 'Method with the same system_name exists.' 26 | end 27 | 28 | def missing_metrics 29 | @missing_metrics ||= ThreeScaleToolbox::Helper.array_difference(source.metrics, target.metrics) do |s_m, t_m| 30 | s_m.system_name == t_m.system_name 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/copy_policies_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class CopyPoliciesTask 6 | include Task 7 | 8 | def call 9 | logger.info 'copy proxy policies' 10 | source_policies = source.policies 11 | target.update_policies('policies_config' => source_policies) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/copy_pricingrules_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class CopyPricingRulesTask 6 | include Task 7 | 8 | def call 9 | plan_mapping = Helper.application_plan_mapping(source.plans, target.plans) 10 | plan_mapping.each do |source_plan, target_plan| 11 | missing_pricing_rules = compute_missing_pricing_rules(source_plan.pricing_rules, target_plan.pricing_rules) 12 | missing_pricing_rules.each do |pricing_rule| 13 | target_plan.create_pricing_rule(metrics_map.fetch(pricing_rule.metric_id), pricing_rule.attrs) 14 | end 15 | logger.info "Missing #{missing_pricing_rules.size} pricing rules from target application plan " \ 16 | "#{target_plan.id}. Source plan #{source_plan.id}" 17 | 18 | plans_report[target_plan.system_name] = {'application_plan_id' => target_plan.id} unless plans_report.has_key? target_plan.system_name 19 | plans_report[target_plan.system_name].merge!({'missing_pricing_rules_created' => missing_pricing_rules.size}) 20 | end 21 | end 22 | 23 | private 24 | 25 | def metrics_map 26 | @metrics_map ||= source.metrics_mapping(target) 27 | end 28 | 29 | def compute_missing_pricing_rules(source_pricing_rules, target_pricing_rules) 30 | ThreeScaleToolbox::Helper.array_difference(source_pricing_rules, target_pricing_rules) do |src, target_pr| 31 | src.cost_per_unit == target_pr.cost_per_unit && 32 | src.min == target_pr.min && 33 | src.max == target_pr.max && 34 | metrics_map.fetch(src.metric_id) == target_pr.metric_id 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/copy_service_proxy_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class CopyServiceProxyTask 6 | include Task 7 | 8 | def call 9 | target.update_proxy target_proxy_attrs 10 | target.update_oidc source.oidc if source.attrs['backend_version'] == 'oidc' 11 | logger.info "updated proxy of #{target.id} to match the original" 12 | end 13 | 14 | def target_proxy_attrs 15 | if source.attrs['deployment_option'] == 'hosted' 16 | # For services with "hosted" deployment config, 17 | # "Public Base URL" should not be copied, mainly because public base URL is self-assigned. 18 | # Two 3scale products (aka services) cannot be served using the same public base URL. 19 | source_proxy.dup.delete_if { |key, _v| %w[endpoint sandbox_endpoint].include? key } 20 | else 21 | source_proxy 22 | end 23 | end 24 | 25 | def source_proxy 26 | @source_proxy ||= source.proxy 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/create_or_update_service_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class CreateOrUpdateTargetServiceTask 6 | include Task 7 | 8 | def call 9 | service = Entities::Service.find(remote: target_remote, 10 | ref: target_service_ref) 11 | if service == source 12 | raise ThreeScaleToolbox::Error, 'Source and destination services are the same: ' \ 13 | "ID: #{source.id} system_name: #{source.attrs['system_name']}" 14 | end 15 | 16 | if service.nil? 17 | service = Entities::Service.create(remote: target_remote, 18 | service_params: create_attrs) 19 | # Notify that mapping rules should be deleted before being copied 20 | force_delete_mapping_rules 21 | else 22 | service.update update_attrs 23 | end 24 | 25 | # assign target service for other tasks to have it available 26 | self.target = service 27 | 28 | logger.info "new service id #{service.id}" 29 | report['product_id'] = service.id 30 | end 31 | 32 | private 33 | 34 | def target_service_ref 35 | option_target_system_name || source.attrs.fetch('system_name') 36 | end 37 | 38 | def create_attrs 39 | source.attrs.merge('system_name' => target_service_ref) 40 | end 41 | 42 | def update_attrs 43 | source.attrs.merge('system_name' => target_service_ref) 44 | end 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/copy_command/destroy_mapping_rules_task.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | module CopyCommand 5 | class DestroyMappingRulesTask 6 | attr_reader :context 7 | 8 | def initialize(context) 9 | @context = context 10 | end 11 | 12 | def call 13 | return unless delete_mapping_rules 14 | 15 | logger.info 'destroying all mapping rules' 16 | target.mapping_rules.each(&:delete) 17 | end 18 | 19 | private 20 | 21 | def delete_mapping_rules 22 | context.fetch(:delete_mapping_rules, false) 23 | end 24 | 25 | def target 26 | context.fetch(:target) 27 | end 28 | 29 | def logger 30 | context[:logger] ||= Logger.new($stdout).tap do |logger| 31 | logger.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" } 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/delete_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | class DeleteSubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | def self.command 8 | Cri::Command.define do 9 | name 'delete' 10 | usage 'delete ' 11 | summary 'Delete a service' 12 | description 'Delete a service' 13 | runner DeleteSubcommand 14 | 15 | param :remote 16 | param :service_id_or_system_name 17 | end 18 | end 19 | 20 | def run 21 | service.delete 22 | puts "Service with id: #{service.id} deleted" 23 | end 24 | 25 | private 26 | 27 | def remote 28 | @remote ||= threescale_client(arguments[:remote]) 29 | end 30 | 31 | def ref 32 | @ref ||= arguments[:service_id_or_system_name] 33 | end 34 | 35 | def service 36 | @service ||= find_service 37 | end 38 | 39 | def find_service 40 | Entities::Service::find(remote: remote, ref: ref).tap do |svc| 41 | raise ThreeScaleToolbox::Error, "Service #{ref} does not exist" if svc.nil? 42 | end 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/list_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | class ListSubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | FIELDS = %w[id name system_name] 8 | 9 | def self.command 10 | Cri::Command.define do 11 | name 'list' 12 | usage 'list ' 13 | summary 'List all services' 14 | description 'List all services' 15 | 16 | ThreeScaleToolbox::CLI.output_flag(self) 17 | param :remote 18 | 19 | runner ListSubcommand 20 | end 21 | end 22 | 23 | def run 24 | printer.print_collection remote.list_services 25 | end 26 | 27 | private 28 | 29 | def remote 30 | @remote ||= threescale_client(arguments[:remote]) 31 | end 32 | 33 | def printer 34 | # keep backwards compatibility 35 | options.fetch(:output, CLI::CustomTablePrinter.new(FIELDS)) 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/commands/service_command/show_command.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Commands 3 | module ServiceCommand 4 | class ShowSubcommand < Cri::CommandRunner 5 | include ThreeScaleToolbox::Command 6 | 7 | FIELDS = %w[ 8 | id name state system_name backend_version deployment_option support_email description 9 | created_at updated_at 10 | ] 11 | 12 | def self.command 13 | Cri::Command.define do 14 | name 'show' 15 | usage 'show ' 16 | summary 'Show the information of a service' 17 | description "Show the information of a service" 18 | 19 | ThreeScaleToolbox::CLI.output_flag(self) 20 | param :remote 21 | param :service_id_or_system_name 22 | 23 | runner ShowSubcommand 24 | end 25 | end 26 | 27 | def run 28 | printer.print_record service.attrs 29 | end 30 | 31 | private 32 | 33 | def remote 34 | @remote ||= threescale_client(arguments[:remote]) 35 | end 36 | 37 | def ref 38 | @ref ||= arguments[:service_id_or_system_name] 39 | end 40 | 41 | def service 42 | @service ||= find_service 43 | end 44 | 45 | def find_service 46 | Entities::Service::find(remote: remote, ref: ref).tap do |svc| 47 | raise ThreeScaleToolbox::Error, "Service #{ref} does not exist" if svc.nil? 48 | end 49 | end 50 | 51 | def printer 52 | options.fetch(:output, CLI::CustomTablePrinter.new(FIELDS)) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/configuration.rb: -------------------------------------------------------------------------------- 1 | require 'yaml/store' 2 | 3 | module ThreeScaleToolbox 4 | class Configuration 5 | attr_reader :config_file 6 | 7 | def initialize(config_file) 8 | @config_file = config_file 9 | @store = YAML::Store.new(config_file) 10 | end 11 | 12 | def data(key) 13 | read[key] 14 | end 15 | 16 | def update(key) 17 | return if key.nil? 18 | 19 | @store.transaction do 20 | @store[key] = yield @store[key] 21 | end 22 | end 23 | 24 | private 25 | 26 | # returns copy of data stored 27 | def read 28 | @store.transaction(true) do 29 | @store.roots.each_with_object({}) do |key, obj| 30 | obj[key] = @store[key] 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/crds/application_plan_dump' 2 | require '3scale_toolbox/crds/backend_method_dump' 3 | require '3scale_toolbox/crds/backend_mapping_rule_dump' 4 | require '3scale_toolbox/crds/backend_metric_dump' 5 | require '3scale_toolbox/crds/backend_dump' 6 | require '3scale_toolbox/crds/backend_usage_dump' 7 | require '3scale_toolbox/crds/limit_dump' 8 | require '3scale_toolbox/crds/mapping_rule_dump' 9 | require '3scale_toolbox/crds/method_dump' 10 | require '3scale_toolbox/crds/metric_dump' 11 | require '3scale_toolbox/crds/pricing_rule_dump' 12 | require '3scale_toolbox/crds/product_dump' 13 | require '3scale_toolbox/crds/product_deployment_parser' 14 | require '3scale_toolbox/crds/backend_parser' 15 | require '3scale_toolbox/crds/product_parser' 16 | require '3scale_toolbox/crds/remote' 17 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/application_plan_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module ApplicationPlanSerializer 4 | def to_cr 5 | { 6 | 'name' => name, 7 | 'appsRequireApproval' => approval_required?, 8 | 'trialPeriod' => trial_period_days, 9 | 'setupFee' => setup_fee, 10 | 'custom' => custom, 11 | 'state' => state, 12 | 'costMonth' => cost_per_month, 13 | 'pricingRules' => pricing_rules.map(&:to_cr), 14 | 'limits' => limits.map(&:to_cr) 15 | } 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/backend_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module BackendSerializer 4 | def to_cr 5 | { 6 | 'apiVersion' => 'capabilities.3scale.net/v1beta1', 7 | 'kind' => 'Backend', 8 | 'metadata' => { 9 | 'annotations' => { 10 | '3scale_toolbox_created_at' => Time.now.utc.iso8601, 11 | '3scale_toolbox_version' => ThreeScaleToolbox::VERSION 12 | }, 13 | 'name' => cr_name 14 | }, 15 | 'spec' => { 16 | 'name' => name, 17 | 'systemName' => system_name, 18 | 'privateBaseURL' => private_endpoint, 19 | 'description' => description, 20 | 'mappingRules' => mapping_rules.map(&:to_cr), 21 | 'metrics' => metrics.each_with_object({}) do |metric, hash| 22 | hash[metric.system_name] = metric.to_cr 23 | end, 24 | 'methods' => methods.each_with_object({}) do |method, hash| 25 | hash[method.system_name] = method.to_cr 26 | end 27 | } 28 | } 29 | end 30 | 31 | def cr_name 32 | # Should be DNS1123 subdomain name 33 | # TODO run validation for DNS1123 34 | # https://kubernetes.io/docs/concepts/overview/working-with-objects/names/ 35 | "#{system_name.gsub(/[^[a-zA-Z0-9\-\.]]/, '.')}.#{Helper.random_lowercase_name}" 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/backend_mapping_rule_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module BackendMappingRuleSerializer 4 | def to_cr 5 | { 6 | 'httpMethod' => http_method, 7 | 'pattern' => pattern, 8 | 'metricMethodRef' => metric_method_ref, 9 | 'increment' => delta, 10 | 'last' => last, 11 | } 12 | end 13 | 14 | def metric_method_ref 15 | if (method = backend.methods.find { |m| m.id == metric_id }) 16 | method.system_name 17 | elsif (metric = backend.metrics.find { |m| m.id == metric_id }) 18 | metric.system_name 19 | else 20 | raise ThreeScaleToolbox::Error, "Unexpected error. Backend #{backend.system_name} " \ 21 | "mapping rule #{id} referencing to metric id #{metric_id} which has not been found" 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/backend_method_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module BackendMethodSerializer 4 | def to_cr 5 | { 6 | 'friendlyName' => friendly_name, 7 | 'description' => description, 8 | } 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/backend_metric_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module BackendMetricSerializer 4 | def to_cr 5 | { 6 | 'friendlyName' => friendly_name, 7 | 'unit' => unit, 8 | 'description' => description, 9 | } 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/backend_parser.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | class BackendParser 4 | Metric = Struct.new(:system_name, :friendly_name, :description, :unit) 5 | Method = Struct.new(:system_name, :friendly_name, :description) 6 | MappingRule = Struct.new(:metric_ref, :http_method, :pattern, :delta, :last) 7 | 8 | attr_reader :cr 9 | 10 | def initialize(cr) 11 | @cr = cr 12 | end 13 | 14 | def system_name 15 | cr.dig('spec', 'systemName') 16 | end 17 | 18 | def name 19 | cr.dig('spec', 'name') 20 | end 21 | 22 | def description 23 | cr.dig('spec', 'description') 24 | end 25 | 26 | def private_endpoint 27 | cr.dig('spec', 'privateBaseURL') 28 | end 29 | 30 | def metrics 31 | @metrics ||= (cr.dig('spec', 'metrics') || {}).map do |system_name, metric| 32 | Metric.new(system_name, metric['friendlyName'], metric['description'], metric['unit']) 33 | end 34 | end 35 | 36 | def methods 37 | @methods ||= (cr.dig('spec', 'methods') || {}).map do |system_name, method| 38 | Method.new(system_name, method['friendlyName'], method['description']) 39 | end 40 | end 41 | 42 | def mapping_rules 43 | @mapping_rules ||= (cr.dig('spec', 'mappingRules') || []).map do |mapping_rule| 44 | MappingRule.new(mapping_rule['metricMethodRef'], mapping_rule['httpMethod'], 45 | mapping_rule['pattern'], mapping_rule['increment'], mapping_rule['last']) 46 | end 47 | end 48 | 49 | # Metrics and methods index by system_name 50 | def metrics_index 51 | @metrics_index ||= (methods + metrics).each_with_object({}) { |metric, h| h[metric.system_name] = metric } 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/backend_usage_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module BackendUsageSerializer 4 | def to_cr 5 | { 6 | 'path' => path 7 | } 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/limit_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module Limit 4 | def to_cr 5 | { 6 | 'period' => period, 7 | 'value' => value, 8 | 'metricMethodRef' => metric_system_name, 9 | } 10 | end 11 | 12 | def metric_system_name 13 | # Find in service methods 14 | # Find in service metrics 15 | # Find in backend methods 16 | # Find in backend metrics 17 | if (method = plan.service.methods.find { |m| m.id == metric_id }) 18 | { 'systemName' => method.system_name } 19 | elsif (metric = plan.service.metrics.find { |m| m.id == metric_id }) 20 | { 'systemName' => metric.system_name } 21 | elsif (backend = backend_from_metric_link) 22 | if (backend_metric = backend.metrics.find { |m| m.id == metric_id }) 23 | { 'systemName' => backend_metric.system_name, 'backend' => backend.system_name } 24 | elsif (backend_method = backend.methods.find { |m| m.id == metric_id }) 25 | { 'systemName' => backend_method.system_name, 'backend' => backend.system_name } 26 | else 27 | raise ThreeScaleToolbox::Error, "Unexpected error. Limit #{id} " \ 28 | "referencing to metric id #{metric_id} which has not been found" 29 | end 30 | else 31 | raise ThreeScaleToolbox::Error, "Unexpected error. Limit #{id} " \ 32 | "referencing to metric id #{metric_id} which has not been found" 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/mapping_rule_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module MappingRuleSerializer 4 | def to_cr 5 | { 6 | 'httpMethod' => http_method, 7 | 'pattern' => pattern, 8 | 'metricMethodRef' => metric_method_ref, 9 | 'increment' => delta, 10 | 'last' => last, 11 | } 12 | end 13 | 14 | def metric_method_ref 15 | if (method = service.methods.find { |m| m.id == metric_id }) 16 | method.system_name 17 | elsif (metric = service.metrics.find { |m| m.id == metric_id }) 18 | metric.system_name 19 | else 20 | raise ThreeScaleToolbox::Error, "Unexpected error. Service #{service.system_name} " \ 21 | "mapping rule #{id} referencing to metric id #{metric_id} which has not been found" 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/method_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module MethodSerializer 4 | def to_cr 5 | { 6 | 'friendlyName' => friendly_name, 7 | 'description' => description, 8 | } 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/metric_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module MetricSerializer 4 | def to_cr 5 | { 6 | 'friendlyName' => friendly_name, 7 | 'unit' => unit, 8 | 'description' => description, 9 | } 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/crds/pricing_rule_dump.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module CRD 3 | module PricingRuleSerializer 4 | def to_cr 5 | { 6 | 'from' => min, 7 | 'to' => max, 8 | 'pricePerUnit' => cost_per_unit, 9 | 'metricMethodRef' => metric_system_name, 10 | } 11 | end 12 | 13 | def metric_system_name 14 | # Find in service methods 15 | # Find in service metrics 16 | # Find in backend methods 17 | # Find in backend metrics 18 | if (method = plan.service.methods.find { |m| m.id == metric_id }) 19 | { 'systemName' => method.system_name } 20 | elsif (metric = plan.service.metrics.find { |m| m.id == metric_id }) 21 | { 'systemName' => metric.system_name } 22 | elsif (backend = backend_from_metric_link) 23 | if (backend_metric = backend.metrics.find { |m| m.id == metric_id }) 24 | { 'systemName' => backend_metric.system_name, 'backend' => backend.system_name } 25 | elsif (backend_method = backend.methods.find { |m| m.id == metric_id }) 26 | { 'systemName' => backend_method.system_name, 'backend' => backend.system_name } 27 | else 28 | raise ThreeScaleToolbox::Error, "Unexpected error. PricingRule #{id} " \ 29 | "referencing to metric id #{metric_id} which has not been found" 30 | end 31 | else 32 | raise ThreeScaleToolbox::Error, "Unexpected error. PricingRule #{id} " \ 33 | "referencing to metric id #{metric_id} which has not been found" 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/entities.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/entities/service' 2 | require '3scale_toolbox/entities/application_plan' 3 | require '3scale_toolbox/entities/metric' 4 | require '3scale_toolbox/entities/method' 5 | require '3scale_toolbox/entities/mapping_rule' 6 | require '3scale_toolbox/entities/limit' 7 | require '3scale_toolbox/entities/pricing_rule' 8 | require '3scale_toolbox/entities/activedocs' 9 | require '3scale_toolbox/entities/account' 10 | require '3scale_toolbox/entities/proxy_config' 11 | require '3scale_toolbox/entities/application' 12 | require '3scale_toolbox/entities/backend' 13 | require '3scale_toolbox/entities/backend_method' 14 | require '3scale_toolbox/entities/backend_metric' 15 | require '3scale_toolbox/entities/backend_mapping_rule' 16 | require '3scale_toolbox/entities/backend_usage' 17 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/entities/base_entity.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Entities 3 | module Entity 4 | PRINTABLE_VARS = %w[ 5 | id 6 | ].freeze 7 | 8 | VERBOSE_PRINTABLE_VARS = %w[ 9 | id 10 | ].freeze 11 | 12 | public_constant :PRINTABLE_VARS 13 | public_constant :VERBOSE_PRINTABLE_VARS 14 | 15 | attr_accessor :verbose 16 | attr_reader :id, :attrs, :remote 17 | 18 | def initialize(id:, remote:, attrs: nil, verbose: false) 19 | @id = id.to_i 20 | @remote = remote 21 | @attrs = attrs 22 | @verbose = verbose 23 | end 24 | 25 | def to_s 26 | if @verbose 27 | format_vars(printable_attrs: self.class.const_get(:VERBOSE_PRINTABLE_VARS, inherit: true)) 28 | else 29 | format_vars(printable_attrs: self.class.const_get(:PRINTABLE_VARS, inherit: true)) 30 | end 31 | end 32 | 33 | private 34 | 35 | def format_vars(printable_attrs: nil) 36 | print_attrs = attrs.merge({ ":id" => @id }) 37 | formatted_vars = printable_attrs.map do |attr| 38 | "#{attr} => #{attrs[attr]}" 39 | end 40 | formatted_vars.join("\n") 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/entities/proxy_config.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | module Entities 3 | class ProxyConfig 4 | class << self 5 | def find(service:, environment:, version:) 6 | new(service: service, environment: environment, version: version).tap(&:attrs) 7 | rescue ThreeScale::API::HttpClient::NotFoundError 8 | nil 9 | end 10 | 11 | def find_latest(service:, environment:) 12 | proxy_cfg = service.remote.proxy_config_latest(service.id, environment) 13 | if (errors = proxy_cfg['errors']) 14 | raise ThreeScaleToolbox::ThreeScaleApiError.new('ProxyConfig find_latest not read', errors) 15 | end 16 | new(service: service, environment: environment, version: proxy_cfg["version"], attrs: proxy_cfg) 17 | rescue ThreeScale::API::HttpClient::NotFoundError 18 | nil 19 | end 20 | end 21 | 22 | attr_reader :remote, :service, :environment, :version 23 | 24 | def initialize(environment:, service:, version:, attrs: nil) 25 | @remote = service.remote 26 | @service = service 27 | @environment = environment 28 | @version = version 29 | @attrs = attrs 30 | end 31 | 32 | def attrs 33 | @attrs ||= proxy_config_attrs 34 | end 35 | 36 | def promote(to:) 37 | res = remote.promote_proxy_config(service.id, environment, version, to) 38 | 39 | if (errors = res['errors']) 40 | raise ThreeScaleToolbox::ThreeScaleApiError.new('ProxyConfig not promoted', errors) 41 | end 42 | res 43 | end 44 | 45 | private 46 | 47 | def proxy_config_attrs 48 | proxy_cfg = remote.show_proxy_config(service.id, environment, version) 49 | 50 | if (errors = proxy_cfg['errors']) 51 | raise ThreeScaleToolbox::ThreeScaleApiError.new('ProxyConfig not read', errors) 52 | end 53 | proxy_cfg 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/error.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | # Generic error. Superclass for all specific errors. 3 | class Error < ::StandardError 4 | def code 5 | 'E_3SCALE' 6 | end 7 | 8 | def kind 9 | self.class 10 | end 11 | 12 | def stacktrace 13 | # For managed errors, stacktrace should not be necessary 14 | nil 15 | end 16 | end 17 | 18 | class InvalidUrlError < Error 19 | def code 20 | 'E_INVALID_URL' 21 | end 22 | end 23 | 24 | class ActiveDocsNotFoundError < Error 25 | attr_reader :id 26 | 27 | def initialize(id) 28 | super("ActiveDocs with ID #{id} not found") 29 | end 30 | 31 | def code 32 | 'E_ACTIVEDOCS_NOT_FOUND' 33 | end 34 | end 35 | 36 | class ThreeScaleApiError < Error 37 | attr_reader :apierrors 38 | 39 | def initialize(msg = '', apierrors = {}) 40 | @apierrors = apierrors 41 | super(msg) 42 | end 43 | 44 | def message 45 | "#{super}. Errors: #{apierrors}" 46 | end 47 | 48 | def code 49 | 'E_3SCALE_API' 50 | end 51 | end 52 | 53 | class InvalidIdError < Error 54 | def code 55 | 'E_INVALID_ID' 56 | end 57 | end 58 | 59 | class UnexpectedError < ::StandardError 60 | attr_reader :unexpectederror 61 | 62 | def initialize(err) 63 | @unexpectederror = err 64 | end 65 | 66 | def message 67 | unexpectederror.message 68 | end 69 | 70 | def kind 71 | unexpectederror.class 72 | end 73 | 74 | def code 75 | 'E_UNKNOWN' 76 | end 77 | 78 | def stacktrace 79 | unexpectederror.backtrace 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/openapi.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/openapi/swagger' 2 | require '3scale_toolbox/openapi/oas3' 3 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/proxy_logger.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | class ProxyLogger < BasicObject 3 | def initialize(subject) 4 | @subject = subject 5 | end 6 | 7 | def method_missing(name, *args) 8 | # Correct delegation https://eregon.me/blog/2021/02/13/correct-delegation-in-ruby-2-27-3.html 9 | start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) 10 | result = @subject.public_send(name, *args) 11 | ensure 12 | end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_time 13 | ::Kernel.warn "-- call #{name} args |#{args.inspect[0..2000]}| response |#{result.inspect[0..2000]}| - (#{end_time}s)" 14 | result 15 | end 16 | ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true) 17 | 18 | def public_send(name, *args) 19 | method_missing(name, *args) 20 | end 21 | 22 | def respond_to_missing?(method_name, include_private = false) 23 | super 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/3scale_toolbox/version.rb: -------------------------------------------------------------------------------- 1 | module ThreeScaleToolbox 2 | VERSION = '1.0.1' 3 | end 4 | -------------------------------------------------------------------------------- /spec/custom_matchers.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/expectations' 2 | 3 | RSpec::Matchers.define :be_subset_of do |superset| 4 | match do |subset| 5 | subset.all? do |subset_elem| 6 | superset.find do |super_set_elem| 7 | ThreeScaleToolbox::Helper.compare_hashes(subset_elem, super_set_elem, @keys) 8 | end 9 | end 10 | end 11 | 12 | chain :comparing_keys do |keys| 13 | @keys = keys 14 | end 15 | end 16 | 17 | RSpec::Matchers.define :excluding_policies do |policy_name| 18 | match do |policies_object| 19 | return true unless policies_object.key?('policies_config') 20 | 21 | policies_object['policies_config'].all? do |policy| 22 | policy[:name] != policy_name 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/integration/commands/activedocs_command/delete_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'ActiveDocs Delete command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | include_context :resources 5 | subject { ThreeScaleToolbox::CLI.run(command_line_str.split) } 6 | let(:remote) { client_url } 7 | 8 | context 'with the specified activedocs not existing' do 9 | let (:activedocs_ref) { "activedocs_sysname_#{random_lowercase_name}" } 10 | let (:command_line_str) { "activedocs delete #{remote} #{activedocs_ref}" } 11 | 12 | it "fails to delete the activedocs" do 13 | expect(subject).not_to eq(0) 14 | end 15 | end 16 | 17 | context 'with an existing activedocs' do 18 | let (:activedocs_ref) { "activedocs_sysname_#{random_lowercase_name}" } 19 | let (:command_line_str) { "activedocs delete #{remote} #{activedocs_ref}" } 20 | let (:activedocs_file) { File.join(resources_path, 'valid_swagger.yaml') } 21 | let (:activedocs_body_pretty_json) do 22 | activedoc_body_content = YAML.load_file(activedocs_file) 23 | JSON.pretty_generate(activedoc_body_content) 24 | end 25 | 26 | before :example do 27 | ThreeScaleToolbox::Entities::ActiveDocs::create(remote: api3scale_client, attrs: { "name" => activedocs_ref, "body" => activedocs_body_pretty_json }) 28 | end 29 | 30 | it "deletes it" do 31 | expect(subject).to eq(0) 32 | res = ThreeScaleToolbox::Entities::ActiveDocs::find(remote: api3scale_client, ref: activedocs_ref) 33 | expect(res).to be_nil 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/integration/commands/import_command/openapi/backend_api_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'OpenAPI import backend api related parameters test' do 2 | include_context :oas_common_context 3 | 4 | let(:expected_backend_api_secret_token) { "secret_token" } 5 | let(:expected_backend_api_hostname_rewrite) { "backendapihost.com" } 6 | let(:oas_resource_path) { File.join(resources_path, 'petstore.yaml') } 7 | let(:command_line_str) do 8 | "import openapi -t #{system_name} -d #{destination_url}" \ 9 | " --backend-api-secret-token=#{expected_backend_api_secret_token}" \ 10 | " --backend-api-host-header=#{expected_backend_api_hostname_rewrite}" \ 11 | " #{oas_resource_path}" 12 | end 13 | 14 | it 'expected backend api configuration options are set' do 15 | expect(subject).to eq(0) 16 | 17 | expect(service_proxy).to include( 18 | 'secret_token' => expected_backend_api_secret_token, 19 | 'hostname_rewrite' => expected_backend_api_hostname_rewrite, 20 | ) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/integration/commands/import_command/openapi/basepath_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'OpenAPI import basepath diff' do 2 | include_context :oas_common_context 3 | 4 | let(:oas_resource_path) { File.join(resources_path, 'petstore.yaml') } 5 | 6 | let(:command_line_str) do 7 | "import openapi -t #{system_name} -d #{destination_url}" \ 8 | ' --override-private-basepath=/private' \ 9 | ' --override-public-basepath=/public' \ 10 | " #{oas_resource_path}" 11 | end 12 | 13 | let(:backend_version) { '1' } 14 | let(:path) { '/public/pet/findByStatus' } 15 | let(:sandbox_host) { service_proxy.fetch('sandbox_endpoint') } 16 | let(:account_name) { "account_#{random_lowercase_name}" } 17 | let(:account) { api3scale_client.signup(name: account_name, username: account_name) } 18 | let(:application_plan) do 19 | ThreeScaleToolbox::Entities::ApplicationPlan.create(service: service, plan_attrs: {'name' => "appplan_#{random_lowercase_name}"}) 20 | end 21 | let(:application) do 22 | api3scale_client.create_application(account['id'], plan_id: application_plan.id, user_key: random_lowercase_name) 23 | end 24 | let(:api_key) { application['user_key'] } 25 | 26 | let(:response) do 27 | uri = URI("#{sandbox_host}#{path}") 28 | uri.query = URI.encode_www_form(api_key: api_key) 29 | Net::HTTP.get_response(uri) 30 | end 31 | 32 | after :example do 33 | api3scale_client.delete_application(account['id'], application['id']) 34 | api3scale_client.delete_account(account['id']) 35 | end 36 | 37 | it 'request url is rewritten' do 38 | expect(subject).to eq(0) 39 | expect(response.class).to be(Net::HTTPOK) 40 | expect(JSON.parse(response.body)).to include('path' => '/private/pet/findByStatus') 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/integration/commands/import_command/openapi/oidc_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'OpenAPI import OIDC service' do 2 | include_context :oas_common_context 3 | 4 | # render from template to avoid system_name collision 5 | let(:oas_resource_path) { File.join(resources_path, 'oidc.yaml') } 6 | let(:issuer_endpoint) { 'https://example.com' } 7 | let(:command_line_str) do 8 | "import openapi -t #{system_name} --oidc-issuer-endpoint=#{issuer_endpoint} " \ 9 | " -d #{destination_url} #{oas_resource_path}" 10 | end 11 | let(:backend_version) { 'oidc' } 12 | let(:credentials_location) { 'headers' } 13 | 14 | it 'oidc settings are updated' do 15 | expect(subject).to eq(0) 16 | expect(service_settings).not_to be_nil 17 | expect(service_settings).to include('backend_version' => backend_version) 18 | expect(service_proxy).not_to be_nil 19 | expect(service_proxy).to include('oidc_issuer_endpoint' => issuer_endpoint, 20 | 'credentials_location' => credentials_location) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/integration/commands/import_command/openapi/prefix_matching_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'OpenAPI prefix matching test' do 2 | include_context :oas_common_context 3 | 4 | let(:oas_resource_path) { File.join(resources_path, 'petstore.yaml') } 5 | let(:command_line_str) do 6 | "import openapi -t #{system_name} -d #{destination_url}" \ 7 | " --prefix-matching" \ 8 | " #{oas_resource_path}" 9 | end 10 | 11 | let(:mapping_rule_keys) { %w[pattern http_method delta] } 12 | 13 | let(:expected_mapping_rules) do 14 | [ 15 | { 'pattern' => '/v2/pet', 'http_method' => 'POST', 'delta' => 1 }, 16 | { 'pattern' => '/v2/pet', 'http_method' => 'PUT', 'delta' => 1 }, 17 | { 'pattern' => '/v2/pet/findByStatus', 'http_method' => 'GET', 'delta' => 1 } 18 | ] 19 | end 20 | 21 | it 'Mapping rules patterns set with prefix matching' do 22 | expect(subject).to eq(0) 23 | 24 | # mapping rules are created 25 | expect(expected_mapping_rules.size).to be > 0 26 | # expect Set(service.mapping_rules) == Set(expected_mapping_rules) 27 | # with a custom identity method for mapping_rules 28 | expect(expected_mapping_rules).to be_subset_of(service.mapping_rules.map(&:attrs)).comparing_keys(mapping_rule_keys) 29 | expect(service.mapping_rules.map(&:attrs)).to be_subset_of(expected_mapping_rules).comparing_keys(mapping_rule_keys) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/integration/commands/methods_command/create_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Method create command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | 5 | let(:method_name) { 'new_name' } 6 | let(:method_ref) { 'my_method_01' } 7 | let(:method_descr) { 'SomeDescr' } 8 | let(:service_ref) { "service_#{random_lowercase_name}" } 9 | let(:command_line_str) do 10 | "method create --disabled -t #{method_ref} --description #{method_descr}" \ 11 | " #{client_url} #{service_ref} #{method_name}" 12 | end 13 | let(:command_line_args) { command_line_str.split } 14 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 15 | let(:service_attrs) { { 'name' => 'API 1', 'system_name' => service_ref } } 16 | let(:service) do 17 | ThreeScaleToolbox::Entities::Service.create( 18 | remote: api3scale_client, service_params: service_attrs 19 | ) 20 | end 21 | let(:plan_ref) { "app_plan_#{random_lowercase_name}" } 22 | 23 | before :example do 24 | plan_attrs = { 'name' => 'old_name', 'system_name' => plan_ref } 25 | ThreeScaleToolbox::Entities::ApplicationPlan.create(service: service, plan_attrs: plan_attrs) 26 | end 27 | 28 | after :example do 29 | service.delete 30 | end 31 | 32 | it 'method is created and disabled' do 33 | expect(subject).to eq(0) 34 | 35 | method = ThreeScaleToolbox::Entities::Method.find(service: service, ref: method_ref) 36 | expect(method).not_to be_nil 37 | expect(method.attrs.fetch('friendly_name')).to eq(method_name) 38 | expect(method.attrs.fetch('description')).to eq(method_descr) 39 | plan = ThreeScaleToolbox::Entities::ApplicationPlan.find(service: service, ref: plan_ref) 40 | expect(plan).not_to be_nil 41 | # check disabled 42 | eternity_zero_limits = plan.metric_limits(method.id).select do |limit| 43 | limit.attrs > { 'period' => 'eternity', 'value' => 0 } 44 | end 45 | expect(eternity_zero_limits).not_to be_empty 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/integration/commands/methods_command/delete_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Method delete command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | 5 | let(:service_ref) { "service_#{random_lowercase_name}" } 6 | let(:command_line_str) { "method delete #{client_url} #{service_ref} #{method_ref}" } 7 | let(:command_line_args) { command_line_str.split } 8 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 9 | let(:service_attrs) { { 'name' => 'API 1', 'system_name' => service_ref } } 10 | let(:service) do 11 | ThreeScaleToolbox::Entities::Service.create( 12 | remote: api3scale_client, service_params: service_attrs 13 | ) 14 | end 15 | let(:method_ref) { "method_#{random_lowercase_name}" } 16 | 17 | before :example do 18 | # add method 19 | method_attrs = { 'system_name' => method_ref, 'friendly_name' => method_ref } 20 | ThreeScaleToolbox::Entities::Method.create(service: service, attrs: method_attrs) 21 | end 22 | 23 | after :example do 24 | service.delete 25 | end 26 | 27 | it 'method is deleted' do 28 | expect(subject).to eq(0) 29 | 30 | expect(ThreeScaleToolbox::Entities::Method.find(service: service, ref: method_ref)).to be_nil 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/integration/commands/methods_command/list_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Method list command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | 5 | let(:service_ref) { "service_#{random_lowercase_name}" } 6 | let(:command_line_str) { "method list #{client_url} #{service_ref}" } 7 | let(:command_line_args) { command_line_str.split } 8 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 9 | let(:service_attrs) { { 'name' => 'API 1', 'system_name' => service_ref } } 10 | let(:service) do 11 | ThreeScaleToolbox::Entities::Service.create( 12 | remote: api3scale_client, service_params: service_attrs 13 | ) 14 | end 15 | let(:method_ref1) { "method_#{random_lowercase_name}" } 16 | let(:method_ref2) { "method_#{random_lowercase_name}" } 17 | 18 | before :example do 19 | # add method 20 | method_attrs = { 'system_name' => method_ref1, 'friendly_name' => method_ref1 } 21 | ThreeScaleToolbox::Entities::Method.create(service: service, attrs: method_attrs) 22 | # add method 23 | method_attrs = { 'system_name' => method_ref2, 'friendly_name' => method_ref2 } 24 | ThreeScaleToolbox::Entities::Method.create(service: service, attrs: method_attrs) 25 | end 26 | 27 | after :example do 28 | service.delete 29 | end 30 | 31 | it 'lists method_ref1' do 32 | expect { subject }.to output(/.*#{method_ref1}.*/).to_stdout 33 | expect(subject).to eq(0) 34 | end 35 | 36 | it 'lists method_ref2' do 37 | expect { subject }.to output(/.*#{method_ref2}.*/).to_stdout 38 | expect(subject).to eq(0) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/integration/commands/metrics_command/create_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Metric create command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | 5 | let(:metric_name) { 'new_name' } 6 | let(:metric_ref) { 'my_metric_01' } 7 | let(:metric_descr) { 'SomeDescr' } 8 | let(:service_ref) { "service_#{random_lowercase_name}" } 9 | let(:command_line_str) do 10 | "metric create --disabled -t #{metric_ref} --description #{metric_descr}" \ 11 | " #{client_url} #{service_ref} #{metric_name}" 12 | end 13 | let(:command_line_args) { command_line_str.split } 14 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 15 | let(:service_attrs) { { 'name' => 'API 1', 'system_name' => service_ref } } 16 | let(:service) do 17 | ThreeScaleToolbox::Entities::Service.create( 18 | remote: api3scale_client, service_params: service_attrs 19 | ) 20 | end 21 | let(:plan_ref) { "app_plan_#{random_lowercase_name}" } 22 | 23 | before :example do 24 | plan_attrs = { 'name' => 'old_name', 'system_name' => plan_ref } 25 | ThreeScaleToolbox::Entities::ApplicationPlan.create(service: service, plan_attrs: plan_attrs) 26 | end 27 | 28 | after :example do 29 | service.delete 30 | end 31 | 32 | it 'metric is created and disabled' do 33 | expect(subject).to eq(0) 34 | 35 | metric = ThreeScaleToolbox::Entities::Metric.find(service: service, ref: metric_ref) 36 | expect(metric).not_to be_nil 37 | expect(metric.attrs.fetch('friendly_name')).to eq(metric_name) 38 | expect(metric.attrs.fetch('description')).to eq(metric_descr) 39 | plan = ThreeScaleToolbox::Entities::ApplicationPlan.find(service: service, ref: plan_ref) 40 | expect(plan).not_to be_nil 41 | # check disabled 42 | eternity_zero_limits = plan.metric_limits(metric.id).select do |limit| 43 | limit.attrs > { 'period' => 'eternity', 'value' => 0 } 44 | end 45 | expect(eternity_zero_limits).not_to be_empty 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/integration/commands/metrics_command/delete_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Metric delete command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | 5 | let(:service_ref) { "service_#{random_lowercase_name}" } 6 | let(:command_line_str) { "metric delete #{client_url} #{service_ref} #{metric_ref}" } 7 | let(:command_line_args) { command_line_str.split } 8 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 9 | let(:service_attrs) { { 'name' => 'API 1', 'system_name' => service_ref } } 10 | let(:service) do 11 | ThreeScaleToolbox::Entities::Service.create( 12 | remote: api3scale_client, service_params: service_attrs 13 | ) 14 | end 15 | let(:metric_ref) { "metric_#{random_lowercase_name}" } 16 | 17 | before :example do 18 | # add metric 19 | metric_attrs = { 'system_name' => metric_ref, 'unit': '1', 'friendly_name' => metric_ref } 20 | ThreeScaleToolbox::Entities::Metric.create(service: service, attrs: metric_attrs) 21 | end 22 | 23 | after :example do 24 | service.delete 25 | end 26 | 27 | it 'metric is deleted' do 28 | expect(subject).to eq(0) 29 | 30 | expect(ThreeScaleToolbox::Entities::Metric.find(service: service, ref: metric_ref)).to be_nil 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/integration/commands/metrics_command/list_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Metric list command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | 5 | let(:service_ref) { "service_#{random_lowercase_name}" } 6 | let(:command_line_str) { "metric list #{client_url} #{service_ref}" } 7 | let(:command_line_args) { command_line_str.split } 8 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 9 | let(:service_attrs) { { 'name' => 'API 1', 'system_name' => service_ref } } 10 | let(:service) do 11 | ThreeScaleToolbox::Entities::Service.create( 12 | remote: api3scale_client, service_params: service_attrs 13 | ) 14 | end 15 | let(:metric_ref1) { "metric_#{random_lowercase_name}" } 16 | let(:metric_ref2) { "metric_#{random_lowercase_name}" } 17 | 18 | before :example do 19 | # add metric 20 | metric_attrs = { 'system_name' => metric_ref1, 'unit' => 1, 'friendly_name' => metric_ref1 } 21 | ThreeScaleToolbox::Entities::Metric.create(service: service, attrs: metric_attrs) 22 | # add metric 23 | metric_attrs = { 'system_name' => metric_ref2, 'unit' => 1, 'friendly_name' => metric_ref2 } 24 | ThreeScaleToolbox::Entities::Metric.create(service: service, attrs: metric_attrs) 25 | end 26 | 27 | after :example do 28 | service.delete 29 | end 30 | 31 | it 'lists metric_ref1' do 32 | expect { subject }.to output(/.*#{metric_ref1}.*/).to_stdout 33 | expect(subject).to eq(0) 34 | end 35 | 36 | it 'lists metric_ref2' do 37 | expect { subject }.to output(/.*#{metric_ref2}.*/).to_stdout 38 | expect(subject).to eq(0) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/integration/commands/plans_command/create_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Application Plan create command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | 5 | let(:cost_per_month) { 10.23 } 6 | let(:app_plan_name) { 'new_name' } 7 | let(:service_ref) { "service_#{random_lowercase_name}" } 8 | let(:plan_ref) { "app_plan_#{random_lowercase_name}" } 9 | let(:command_line_str) do 10 | "application-plan create --cost-per-month=#{cost_per_month}" \ 11 | " --publish --disabled -t #{app_plan_name}" \ 12 | " #{client_url} #{service_ref} #{app_plan_name}" 13 | end 14 | let(:command_line_args) { command_line_str.split } 15 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 16 | let(:service_attrs) { { 'name' => 'API 1', 'system_name' => service_ref } } 17 | let(:service) do 18 | ThreeScaleToolbox::Entities::Service.create( 19 | remote: api3scale_client, service_params: service_attrs 20 | ) 21 | end 22 | 23 | before :example do 24 | # add method 25 | method_attrs = { 'system_name' => 'method_01', 'friendly_name' => 'method_01' } 26 | ThreeScaleToolbox::Entities::Method.create(service: service, attrs: method_attrs) 27 | end 28 | 29 | after :example do 30 | service.delete 31 | end 32 | 33 | it 'application plan is published and enabled' do 34 | expect(subject).to eq(0) 35 | 36 | plan = ThreeScaleToolbox::Entities::ApplicationPlan.find(service: service, ref: app_plan_name) 37 | 38 | expect(plan).not_to be_nil 39 | # check name has been changed 40 | expect(plan.attrs.fetch('name')).to eq(app_plan_name) 41 | # check published 42 | expect(plan.published?).to be_truthy 43 | # check disabled 44 | zero_eternity_limit_attrs = { 'period' => 'eternity', 'value' => 0 } 45 | eternity_zero_limits = plan.limits.select { |limit| zero_eternity_limit_attrs < limit.attrs } 46 | expect(eternity_zero_limits).not_to be_empty 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/integration/commands/plans_command/delete_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Application Plan delete command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | 5 | let(:service_ref) { "service_#{random_lowercase_name}" } 6 | let(:plan_ref) { "app_plan_#{random_lowercase_name}" } 7 | let(:command_line_str) do 8 | 'application-plan delete' \ 9 | " #{client_url} #{service_ref} #{plan_ref}" 10 | end 11 | let(:command_line_args) { command_line_str.split } 12 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 13 | let(:service_attrs) { { 'name' => 'API 1', 'system_name' => service_ref } } 14 | let(:service) do 15 | ThreeScaleToolbox::Entities::Service.create( 16 | remote: api3scale_client, service_params: service_attrs 17 | ) 18 | end 19 | let(:plan_attrs) { { 'name' => 'old_name', 'system_name' => plan_ref } } 20 | 21 | before :example do 22 | # Create application plan (hence update will be done) 23 | ThreeScaleToolbox::Entities::ApplicationPlan.create(service: service, plan_attrs: plan_attrs) 24 | end 25 | 26 | after :example do 27 | service.delete 28 | end 29 | 30 | it 'application plan is deleted' do 31 | expect(subject).to eq(0) 32 | 33 | expect(ThreeScaleToolbox::Entities::ApplicationPlan.find(service: service, ref: plan_ref)).to be_nil 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/integration/commands/plans_command/list_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Application Plan list command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | 5 | let(:service_ref) { "service_#{random_lowercase_name}" } 6 | let(:command_line_str) do 7 | "application-plan list #{client_url} #{service_ref}" 8 | end 9 | let(:command_line_args) { command_line_str.split } 10 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 11 | 12 | let(:service_attrs) { { 'name' => 'API 1', 'system_name' => service_ref } } 13 | let(:service) do 14 | ThreeScaleToolbox::Entities::Service.create( 15 | remote: api3scale_client, service_params: service_attrs 16 | ) 17 | end 18 | let(:plan_ref1) { "app_plan_#{random_lowercase_name}" } 19 | let(:plan_ref2) { "app_plan_#{random_lowercase_name}" } 20 | 21 | before :example do 22 | # Create application plan 23 | plan_attrs = { 'name' => 'name1', 'system_name' => plan_ref1 } 24 | ThreeScaleToolbox::Entities::ApplicationPlan.create(service: service, plan_attrs: plan_attrs) 25 | # Create application plan 26 | plan_attrs = { 'name' => 'name2', 'system_name' => plan_ref2 } 27 | ThreeScaleToolbox::Entities::ApplicationPlan.create(service: service, plan_attrs: plan_attrs) 28 | end 29 | 30 | after :example do 31 | service.delete 32 | end 33 | 34 | it 'lists plan_ref1' do 35 | expect { subject }.to output(/.*#{plan_ref1}.*/).to_stdout 36 | expect(subject).to eq(0) 37 | end 38 | 39 | it 'lists plan_ref2' do 40 | expect { subject }.to output(/.*#{plan_ref2}.*/).to_stdout 41 | expect(subject).to eq(0) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/integration/commands/plans_command/show_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Application Plan show command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | 5 | let(:plan_ref) { "app_plan_#{random_lowercase_name}" } 6 | let(:service_ref) { "service_#{random_lowercase_name}" } 7 | let(:command_line_str) do 8 | "application-plan show #{client_url} #{service_ref} #{plan_ref}" 9 | end 10 | let(:command_line_args) { command_line_str.split } 11 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 12 | 13 | let(:service_attrs) { { 'name' => 'API 1', 'system_name' => service_ref } } 14 | let(:service) do 15 | ThreeScaleToolbox::Entities::Service.create( 16 | remote: api3scale_client, service_params: service_attrs 17 | ) 18 | end 19 | 20 | before :example do 21 | # Create application plan 22 | plan_attrs = { 'name' => 'name1', 'system_name' => plan_ref } 23 | ThreeScaleToolbox::Entities::ApplicationPlan.create(service: service, plan_attrs: plan_attrs) 24 | end 25 | 26 | after :example do 27 | service.delete 28 | end 29 | 30 | it 'lists plan_ref1' do 31 | expect { subject }.to output(/.*#{plan_ref}.*/).to_stdout 32 | expect(subject).to eq(0) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/integration/commands/remote_command/remote_remove_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::RemoteCommand::RemoteRemoveSubcommand do 2 | include_context :resources 3 | include_context :temp_dir 4 | 5 | context '#run' do 6 | let(:config_file) { File.join(tmp_dir, '.3scalerc') } 7 | let(:options) { { 'config-file': config_file } } 8 | let(:arguments) { {} } 9 | subject { described_class.new(options, arguments, nil) } 10 | let(:configuration) { ThreeScaleToolbox::Configuration.new(config_file) } 11 | 12 | context 'remote does not exist' do 13 | let(:arguments) { { remote_name: 'some_remote' } } 14 | 15 | it 'raises error' do 16 | expect { subject.run }.to raise_error(ThreeScaleToolbox::Error, 17 | /could not remove remote/) 18 | end 19 | end 20 | 21 | context 'remote exists' do 22 | let(:config_file) { File.join(tmp_dir, 'valid_config_file.yaml') } 23 | let(:arguments) { { remote_name: 'remote_1' } } 24 | before :each do 25 | # Config file is going to be updated. 26 | # Config file will be a fresh copy of the source 27 | FileUtils.cp(File.join(resources_path, 'valid_config_file.yaml'), 28 | tmp_dir) 29 | end 30 | 31 | it 'after removing is gone' do 32 | subject.run 33 | expect(configuration.data(:remotes)).not_to include('remote_1') 34 | end 35 | 36 | it 'after removing, other remotes still in conf file' do 37 | subject.run 38 | 2.upto(5) do |i| 39 | expect(configuration.data(:remotes)).to include("remote_#{i}") 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/integration/commands/service_command/copy_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Copy Service' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | include_context :real_copy_cleanup 5 | 6 | let(:source_url) { client_url } 7 | let(:destination_url) { client_url } 8 | let(:target_system_name) { "service_#{random_lowercase_name}_#{Time.now.getutc.to_i}" } 9 | let(:command_line_str) do 10 | "copy service -t #{target_system_name}" \ 11 | " -s #{source_url} -d #{destination_url} #{source_service.id}" 12 | end 13 | let(:command_line_args) { command_line_str.split } 14 | subject { ThreeScaleToolbox::CLI.run(command_line_args) } 15 | # source service is being created for testing 16 | let(:source_service) { Helpers::ServiceFactory.new_service api3scale_client } 17 | let(:target_service) do 18 | ThreeScaleToolbox::Entities::Service.find(ref: target_system_name, remote: api3scale_client) 19 | end 20 | 21 | it_behaves_like 'service copied' 22 | end 23 | -------------------------------------------------------------------------------- /spec/integration/commands/service_command/delete_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Service Delete command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | subject { ThreeScaleToolbox::CLI.run(command_line_str.split) } 5 | let(:remote) do 6 | endpoint_uri = URI(endpoint) 7 | endpoint_uri.user = provider_key 8 | endpoint_uri.to_s 9 | end 10 | 11 | context 'with the specified service not existing' do 12 | let (:service_ref) { "service_sysname_#{random_lowercase_name}" } 13 | let (:command_line_str) { "service delete #{remote} #{service_ref}" } 14 | 15 | it "fails to delete the service" do 16 | expect(subject).not_to eq(0) 17 | end 18 | end 19 | 20 | context 'with an existing service' do 21 | let (:service_ref) { "service_sysname_#{random_lowercase_name}" } 22 | let (:command_line_str) { "service delete #{remote} #{service_ref}" } 23 | before :example do 24 | ThreeScaleToolbox::Entities::Service::create(remote: api3scale_client, service_params: { "name" => service_ref }) 25 | end 26 | 27 | it "deletes it" do 28 | expect(subject).to eq(0) 29 | res = ThreeScaleToolbox::Entities::Service::find(remote: api3scale_client, ref: service_ref) 30 | expect(res).to be_nil 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/integration/commands/service_command/list_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Service List command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | subject { ThreeScaleToolbox::CLI.run(command_line_str.split) } 5 | let(:remote) do 6 | endpoint_uri = URI(endpoint) 7 | endpoint_uri.user = provider_key 8 | endpoint_uri.to_s 9 | end 10 | 11 | context "With multiple existing services" do 12 | let (:svc_ref_1) { "service_sysname_#{random_lowercase_name}" } 13 | let (:svc_ref_2) { "service_sysname_#{random_lowercase_name}" } 14 | let (:command_line_str) { "service list #{remote}" } 15 | 16 | before :example do 17 | ThreeScaleToolbox::Entities::Service::create(remote: api3scale_client, service_params: { "name" => svc_ref_1 }) 18 | ThreeScaleToolbox::Entities::Service::create(remote: api3scale_client, service_params: { "name" => svc_ref_2 }) 19 | end 20 | 21 | it "lists svc_ref_1" do 22 | expect { subject }.to output(/.*#{svc_ref_1}.*/).to_stdout 23 | expect(subject).to eq(0) 24 | end 25 | 26 | it "lists svc_ref_2" do 27 | expect { subject }.to output(/.*#{svc_ref_2}.*/).to_stdout 28 | expect(subject).to eq(0) 29 | end 30 | 31 | after :example do 32 | res = ThreeScaleToolbox::Entities::Service::find(remote: api3scale_client, ref: svc_ref_1) 33 | res.delete if !res.nil? 34 | res = ThreeScaleToolbox::Entities::Service::find(remote: api3scale_client, ref: svc_ref_2) 35 | res.delete if !res.nil? 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/integration/commands/service_command/show_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Service Show command' do 2 | include_context :real_api3scale_client 3 | include_context :random_name 4 | subject { ThreeScaleToolbox::CLI.run(command_line_str.split) } 5 | let(:remote) do 6 | endpoint_uri = URI(endpoint) 7 | endpoint_uri.user = provider_key 8 | endpoint_uri.to_s 9 | end 10 | 11 | context "With the specified service not existing" do 12 | let (:service_ref) { "service_sysname_#{random_lowercase_name}" } 13 | let (:command_line_str) { "service show #{remote} #{service_ref}" } 14 | 15 | it "fails to show the service" do 16 | expect(subject).not_to eq(0) 17 | res = ThreeScaleToolbox::Entities::Service::find(remote: api3scale_client, ref: service_ref) 18 | expect(res).to be_nil 19 | end 20 | end 21 | 22 | context "With the specified service existing" do 23 | let (:service_ref) { "service_sysname_#{random_lowercase_name}" } 24 | let (:command_line_str) { "service show #{remote} #{service_ref}" } 25 | 26 | before :example do 27 | ThreeScaleToolbox::Entities::Service::create(remote: api3scale_client, service_params: { "name" => service_ref }) 28 | end 29 | 30 | it "shows the service" do 31 | expect { subject }.to output(/.*#{service_ref}.*/).to_stdout 32 | expect(subject).to eq(0) 33 | res = ThreeScaleToolbox::Entities::Service::find(remote: api3scale_client, ref: service_ref) 34 | expect(res).not_to be_nil 35 | end 36 | 37 | after :example do 38 | res = ThreeScaleToolbox::Entities::Service::find(remote: api3scale_client, ref: service_ref) 39 | res.delete if !res.nil? 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/resources/3scale_toolbox_plugin_template.erb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox/cli' 2 | 3 | class <%= command_class_name %> < Cri::CommandRunner 4 | include ThreeScaleToolbox::Command 5 | 6 | def self.command 7 | Cri::Command.define do 8 | name '<%= command_name %>' 9 | usage '<%= command_name %>' 10 | runner <%= command_class_name %> 11 | end 12 | end 13 | 14 | def run 15 | puts 'this is <%= command_name %> command' 16 | end 17 | end 18 | ThreeScaleToolbox::CLI.add_command(<%= command_class_name %>) 19 | -------------------------------------------------------------------------------- /spec/resources/oidc.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | description: >- 4 | This is a sample server Petstore server. You can find out more about 5 | Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, 6 | #swagger](http://swagger.io/irc/). For this sample, you can use the api key 7 | `special-key` to test the authorization filters. 8 | version: 1.0.0 9 | title: Swagger Petstore 10 | host: petstore.swagger.io:443 11 | basePath: /v2 12 | schemes: 13 | - https 14 | - http 15 | paths: 16 | /pet: 17 | post: 18 | tags: 19 | - pet 20 | summary: Add a new pet to the store 21 | description: '' 22 | operationId: addPet 23 | responses: 24 | '405': 25 | description: Invalid input 26 | security: 27 | - petstore_auth: 28 | - 'write:pets' 29 | 30 | securityDefinitions: 31 | petstore_auth: 32 | type: oauth2 33 | authorizationUrl: 'https://petstore.swagger.io/oauth/authorize' 34 | flow: implicit 35 | scopes: 36 | 'write:pets': modify pets in your account 37 | 'read:pets': read your pets 38 | -------------------------------------------------------------------------------- /spec/resources/plan.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | plan: 3 | name: basic 4 | state: published 5 | setup_fee: 0.0 6 | cost_per_month: 0.0 7 | trial_period_days: 0 8 | cancellation_period: 0 9 | approval_required: false 10 | system_name: basic 11 | limits: 12 | - period: year 13 | value: 10000 14 | metric_system_name: metric_01 15 | - period: year 16 | value: 100 17 | metric_system_name: backend_method_01 18 | metric_backend_system_name: __BACKEND_SYSTEM_NAME__ 19 | pricingrules: 20 | - cost_per_unit: '2.0' 21 | min: 102 22 | max: 200 23 | metric_system_name: method_01 24 | - cost_per_unit: '2.0' 25 | min: 202 26 | max: 300 27 | metric_system_name: backend_metric_01 28 | metric_backend_system_name: __BACKEND_SYSTEM_NAME__ 29 | plan_features: 30 | - name: Unlimited Greetings 31 | system_name: unlimited_greetings 32 | scope: application_plan 33 | visible: true 34 | metrics: 35 | - system_name: metric_01 36 | friendly_name: metric_01 37 | description: Metric01 38 | unit: '1' 39 | - system_name: backend_metric_01 40 | friendly_name: backend_metric_01 41 | description: Backend Metric 01 42 | unit: '1' 43 | backend_system_name: __BACKEND_SYSTEM_NAME__ 44 | methods: 45 | - system_name: method_01 46 | friendly_name: method_01 47 | - system_name: backend_method_01 48 | friendly_name: backend_method_01 49 | backend_system_name: __BACKEND_SYSTEM_NAME__ 50 | created_at: '2019-05-01T07:10:18Z' 51 | toolbox_version: 0.8.0 52 | -------------------------------------------------------------------------------- /spec/resources/valid_config_file.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | :remotes: 3 | remote_1: 4 | :endpoint: https://1.example.com 5 | :authentication: '1' 6 | remote_2: 7 | :endpoint: https://2.example.com 8 | :authentication: '2' 9 | remote_3: 10 | :endpoint: https://3.example.com 11 | :authentication: '3' 12 | remote_4: 13 | :endpoint: https://4.example.com 14 | :authentication: '4' 15 | remote_5: 16 | :endpoint: https://5.example.com 17 | :authentication: '5' 18 | -------------------------------------------------------------------------------- /spec/unit/3scale_toolbox_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox do 2 | include_context :temp_dir 3 | include_context :plugin 4 | include_context :random_name 5 | 6 | let(:name) { random_lowercase_name } 7 | let(:dest_plugin_file) { tmp_dir.join('3scale_toolbox_plugin.rb') } 8 | 9 | around(:each) do |example| 10 | plugin = get_plugin_content(name.capitalize, name) 11 | dest_plugin_file.write(plugin) 12 | $LOAD_PATH.unshift(tmp_dir) unless $LOAD_PATH.include?(tmp_dir) 13 | example.run 14 | $LOAD_PATH.delete(tmp_dir) 15 | end 16 | 17 | context '#plugin_paths' do 18 | it 'finds plugin' do 19 | expect(described_class.plugin_paths).to include(dest_plugin_file.to_s) 20 | end 21 | end 22 | 23 | context '#load_plugins' do 24 | it 'loads plugin' do 25 | expect { described_class.load_plugins }.not_to raise_error 26 | expect(Object.const_get(name.capitalize.to_sym)).to be_truthy 27 | end 28 | end 29 | 30 | context '#default_config_file' do 31 | it 'using ENV var' do 32 | filename = 'some_file_name' 33 | env_copy = ENV.to_h 34 | env_copy['THREESCALE_CLI_CONFIG'] = filename 35 | stub_const('ENV', env_copy) 36 | expect(described_class.default_config_file).to eq filename 37 | end 38 | 39 | it 'default' do 40 | expect(described_class.default_config_file).to eq File.join Gem.user_home, '.3scalerc.yaml' 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/unit/cli/custom_table_printer_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::CLI::CustomTablePrinter do 2 | let(:fields) { %w[my_field_name_a my_field_name_b] } 3 | let(:record_a) { { 'my_field_name_a' => 11, 'my_field_name_b' => 22 } } 4 | let(:record_b) { { 'my_field_name_a' => 33, 'my_field_name_b' => 44 } } 5 | 6 | shared_examples 'header printed' do 7 | it 'header_printed' do 8 | expect { subject }.to output(/#{fields.map(&:upcase).join('\t')}/).to_stdout 9 | end 10 | end 11 | 12 | context '#print_record' do 13 | subject { described_class.new(fields).print_record(record_a) } 14 | 15 | include_examples 'header printed' 16 | 17 | it 'record_a printed' do 18 | expect { subject }.to output(/#{record_a.values.join('\t')}/).to_stdout 19 | end 20 | end 21 | 22 | context '#print_collection' do 23 | subject { described_class.new(fields).print_collection([record_a, record_b]) } 24 | 25 | include_examples 'header printed' 26 | 27 | it 'record_a printed' do 28 | expect { subject }.to output(/#{record_a.values.join('\t')}/).to_stdout 29 | end 30 | 31 | it 'record_b printed' do 32 | expect { subject }.to output(/#{record_b.values.join('\t')}/).to_stdout 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/commands/activedocs_command/delete_command_spec.rb: -------------------------------------------------------------------------------- 1 | require '3scale_toolbox' 2 | 3 | RSpec.describe ThreeScaleToolbox::Commands::ActiveDocsCommand::Delete::DeleteSubcommand do 4 | include_context :random_name 5 | 6 | context '#run' do 7 | let(:remote) { instance_double(ThreeScale::API::Client, 'remote') } 8 | let(:activedocs_class) { class_double(ThreeScaleToolbox::Entities::ActiveDocs).as_stubbed_const } 9 | let(:activedocs) { instance_double(ThreeScaleToolbox::Entities::ActiveDocs) } 10 | let(:remote_name) { "myremote" } 11 | let(:options) {} 12 | 13 | subject { described_class.new(options, arguments, nil) } 14 | 15 | before :example do 16 | expect(subject).to receive(:threescale_client).with(remote_name).and_return(remote) 17 | end 18 | 19 | context "when the activedocs does not exists" do 20 | let(:activedocs_ref) { "unexistingadocs" } 21 | let(:arguments) { { remote: remote_name, activedocs_id_or_system_name: activedocs_ref } } 22 | 23 | it 'an error is raised' do 24 | expect(activedocs_class).to receive(:find).with(remote: remote, ref: activedocs_ref).and_return(nil) 25 | expect do 26 | subject.run 27 | end.to raise_error(ThreeScaleToolbox::Error, /ActiveDocs.*does not exist/) 28 | end 29 | end 30 | 31 | context "when a activedocs exists" do 32 | let(:activedocs_ref) { "existingadocs" } 33 | let(:arguments) { { remote: remote_name, activedocs_id_or_system_name: activedocs_ref } } 34 | 35 | it 'is removed' do 36 | adocs_id = "3" 37 | expect(activedocs).to receive(:delete).and_return(true) 38 | expect(activedocs).to receive(:id).and_return(adocs_id) 39 | expect(activedocs_class).to receive(:find).with(remote: remote, ref: activedocs_ref).and_return(activedocs) 40 | expect do 41 | subject.run 42 | end.to output(/.*ActiveDocs with id: #{adocs_id} deleted.*/).to_stdout 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/unit/commands/application_command/delete_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ApplicationCommand::Delete::DeleteSubcommand do 2 | let(:arguments) { { application_ref: 'someapp', remote: 'https://key@example.com' } } 3 | let(:options) {} 4 | let(:remote) { instance_double('ThreeScale::API::Client', 'remote') } 5 | let(:application_class) { class_double(ThreeScaleToolbox::Entities::Application).as_stubbed_const } 6 | subject { described_class.new(options, arguments, nil) } 7 | 8 | context '#run' do 9 | before :example do 10 | expect(subject).to receive(:threescale_client).and_return(remote) 11 | expect(application_class).to receive(:find).with(remote: remote, ref: 'someapp') 12 | .and_return(application) 13 | end 14 | 15 | context 'when application not found' do 16 | let(:application) { nil } 17 | 18 | it 'error raised' do 19 | expect { subject.run }.to raise_error(ThreeScaleToolbox::Error, /Application someapp does not exist/) 20 | end 21 | end 22 | 23 | context 'when application found' do 24 | let(:application) { instance_double(ThreeScaleToolbox::Entities::Application) } 25 | 26 | before :example do 27 | expect(application).to receive(:id).and_return('1') 28 | end 29 | 30 | it do 31 | expect(application).to receive(:delete) 32 | expect { subject.run }.to output(/Application id: 1 deleted/).to_stdout 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/unit/commands/application_command/show_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ApplicationCommand::Show::ShowSubcommand do 2 | let(:arguments) { { application: 'someapp', remote: 'https://key@destination.example.com' } } 3 | let(:options) { {} } 4 | let(:remote) { instance_double(ThreeScale::API::Client, 'remote') } 5 | let(:application_class) { class_double(ThreeScaleToolbox::Entities::Application).as_stubbed_const } 6 | let(:application) { instance_double(ThreeScaleToolbox::Entities::Application) } 7 | subject { described_class.new(options, arguments, nil) } 8 | context '#run' do 9 | before :example do 10 | expect(subject).to receive(:threescale_client).and_return(remote) 11 | expect(application_class).to receive(:find).with(remote: remote, ref: 'someapp') 12 | .and_return(application) 13 | end 14 | 15 | context 'when application not found' do 16 | let(:application) { nil } 17 | 18 | it 'error raised' do 19 | expect { subject.run }.to raise_error(ThreeScaleToolbox::Error, 20 | /Application someapp does not exist/) 21 | end 22 | end 23 | 24 | context 'when application is found' do 25 | let(:app_attrs) { { 'id' => 'appId', 'name' => 'appA' } } 26 | before :example do 27 | expect(application).to receive(:attrs).and_return(app_attrs) 28 | end 29 | 30 | it 'id is shown' do 31 | expect { subject.run }.to output(/appId/).to_stdout 32 | end 33 | 34 | it 'name is shown' do 35 | expect { subject.run }.to output(/appA/).to_stdout 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/unit/commands/backend_command/copy_command/delete_mapping_rules_task_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::BackendCommand::CopyCommand::DeleteMappingRulesTask do 2 | let(:target) { instance_double('ThreeScaleToolbox::Entities::Service', 'target') } 3 | let(:base_context) { { target_backend: target, logger: Logger.new('/dev/null') } } 4 | let(:task_context) { base_context } 5 | subject { described_class.new(task_context) } 6 | 7 | context '#run' do 8 | context 'delete_mapping_rules flag false' do 9 | let(:task_context) { base_context.merge(delete_mapping_rules: false) } 10 | it 'no op' do 11 | # Run 12 | subject.call 13 | end 14 | end 15 | 16 | context 'several mapping rules available' do 17 | let(:task_context) { base_context.merge(delete_mapping_rules: true) } 18 | let(:n_rules) { 10 } 19 | let(:target_mapping_rules) do 20 | Array.new(n_rules) do |_| 21 | instance_double('ThreeScaleToolbox::Entities::MappingRule') 22 | end 23 | end 24 | 25 | it 'it calls delete_mapping_rule method on each rule' do 26 | expect(target).to receive(:mapping_rules).and_return(target_mapping_rules) 27 | expect(target_mapping_rules.size).to be > 0 28 | target_mapping_rules.each do |mapping_rule| 29 | expect(mapping_rule).to receive(:delete) 30 | end 31 | 32 | # Run 33 | subject.call 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/unit/commands/import_command/openapi/create_mapping_backend_rules_step_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::CreateBackendMappingRulesStep do 2 | let(:backend) { instance_double(ThreeScaleToolbox::Entities::Backend) } 3 | let(:mappingrule_class) { class_double(ThreeScaleToolbox::Entities::BackendMappingRule).as_stubbed_const } 4 | let(:op0) { double('op0') } 5 | let(:op1) { double('op1') } 6 | let(:operations) { [op0, op1] } 7 | let(:openapi_context) do 8 | { 9 | operations: operations, 10 | backend_target: backend, 11 | logger: logger, 12 | } 13 | end 14 | let(:mapping_rule_0) { double('mapping_rule_0') } 15 | let(:mapping_rule_1) { double('mapping_rule_1') } 16 | let(:logger) { Logger.new(File::NULL) } 17 | subject { described_class.new(openapi_context) } 18 | 19 | context '#call' do 20 | before :each do 21 | allow(backend).to receive(:mapping_rules).and_return([]) 22 | 23 | allow(op0).to receive(:mapping_rule).and_return(mapping_rule_0) 24 | allow(op0).to receive(:http_method).and_return('http_method_0') 25 | allow(op0).to receive(:pattern).and_return('pattern_0') 26 | allow(op0).to receive(:friendly_name).and_return('op0') 27 | 28 | allow(op1).to receive(:mapping_rule).and_return(mapping_rule_1) 29 | allow(op1).to receive(:http_method).and_return('http_method_1') 30 | allow(op1).to receive(:pattern).and_return('pattern_1') 31 | allow(op1).to receive(:friendly_name).and_return('op1') 32 | expect(mappingrule_class).to receive(:create).with(backend: backend, 33 | attrs: mapping_rule_0) 34 | expect(mappingrule_class).to receive(:create).with(backend: backend, 35 | attrs: mapping_rule_1) 36 | end 37 | 38 | it 'mapping rules created' do 39 | subject.call 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/unit/commands/import_command/openapi/create_mapping_rules_step_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::CreateMappingRulesStep do 2 | let(:service) { instance_double('ThreeScaleToolbox::Entities::Service') } 3 | let(:mappingrule_class) { class_double(ThreeScaleToolbox::Entities::MappingRule).as_stubbed_const } 4 | let(:op0) { double('op0') } 5 | let(:op1) { double('op1') } 6 | let(:operations) { [op0, op1] } 7 | let(:openapi_context) do 8 | { 9 | operations: operations, 10 | target: service, 11 | logger: logger, 12 | } 13 | end 14 | let(:mapping_rule_0) { double('mapping_rule_0') } 15 | let(:mapping_rule_1) { double('mapping_rule_1') } 16 | let(:logger) { Logger.new(File::NULL) } 17 | subject { described_class.new(openapi_context) } 18 | 19 | context '#call' do 20 | before :each do 21 | allow(op0).to receive(:mapping_rule).and_return(mapping_rule_0) 22 | allow(op0).to receive(:http_method).and_return('http_method_0') 23 | allow(op0).to receive(:pattern).and_return('pattern_0') 24 | allow(op0).to receive(:friendly_name).and_return('op0') 25 | 26 | allow(op1).to receive(:mapping_rule).and_return(mapping_rule_1) 27 | allow(op1).to receive(:http_method).and_return('http_method_1') 28 | allow(op1).to receive(:pattern).and_return('pattern_1') 29 | allow(op1).to receive(:friendly_name).and_return('op1') 30 | expect(mappingrule_class).to receive(:create).with(service: service, 31 | attrs: mapping_rule_0) 32 | expect(mappingrule_class).to receive(:create).with(service: service, 33 | attrs: mapping_rule_1) 34 | end 35 | 36 | it 'mapping rules created' do 37 | subject.call 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/unit/commands/import_command/openapi/import_backend_step_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::ImportBackendStep do 2 | subject { described_class.new(openapi_context) } 3 | let(:openapi_context) { { override_private_base_url: 'https://example.com' } } 4 | 5 | context '#call' do 6 | it 'all required tasks are run' do 7 | # Task stubs 8 | [ 9 | ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::CreateBackendStep, 10 | ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::CreateBackendMethodsStep, 11 | ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::CreateBackendMappingRulesStep, 12 | ].each do |task_class| 13 | task = instance_double(task_class.to_s) 14 | task_class_obj = class_double(task_class).as_stubbed_const 15 | expect(task_class_obj).to receive(:new).and_return(task) 16 | expect(task).to receive(:call) 17 | end 18 | 19 | subject.call 20 | end 21 | 22 | context 'private endpoint not provided' do 23 | let(:api_spec) { double() } 24 | let(:openapi_context) { { api_spec: api_spec, override_private_base_url: nil } } 25 | 26 | before :each do 27 | allow(api_spec).to receive(:host).and_return(nil) 28 | end 29 | 30 | it 'raises error' do 31 | expect { subject.call }.to raise_error(ThreeScaleToolbox::Error, 32 | /private endpoint not specified/) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/unit/commands/import_command/openapi/import_product_step_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::ImportProductStep do 2 | subject { described_class.new(openapi_context) } 3 | let(:openapi_context) { {} } 4 | 5 | context '#call' do 6 | it 'all required tasks are run' do 7 | # Task stubs 8 | [ 9 | ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::CreateServiceStep, 10 | ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::UpdateServiceProxyStep, 11 | ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::CreateMethodsStep, 12 | ThreeScaleToolbox::Commands::ServiceCommand::CopyCommand::DestroyMappingRulesTask, 13 | ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::CreateMappingRulesStep, 14 | ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::CreateActiveDocsStep, 15 | ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::UpdateServiceOidcConfStep, 16 | ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::UpdatePoliciesStep, 17 | ThreeScaleToolbox::Commands::ServiceCommand::CopyCommand::BumpProxyVersionTask, 18 | ].each do |task_class| 19 | task = instance_double(task_class.to_s) 20 | task_class_obj = class_double(task_class).as_stubbed_const 21 | expect(task_class_obj).to receive(:new).and_return(task) 22 | expect(task).to receive(:call) 23 | end 24 | 25 | subject.call 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/commands/import_command/openapi/mapping_rule_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'OpenAPI Mapping Rule' do 2 | class OpenAPIMappingRuleClass 3 | include ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::MappingRule 4 | 5 | attr_reader :operation 6 | 7 | def initialize(operation) 8 | @operation = operation 9 | end 10 | end 11 | 12 | context '#mapping_rule' do 13 | let(:verb) { 'POST' } 14 | let(:path) { '/some/path' } 15 | let(:metric_id) { '1' } 16 | let(:public_base_path) { '/v1' } 17 | let(:prefix_matching) { false } 18 | let(:operation) do 19 | { verb: verb, path: path, metric_id: metric_id, public_base_path: public_base_path, prefix_matching: prefix_matching } 20 | end 21 | subject { OpenAPIMappingRuleClass.new(operation).mapping_rule } 22 | 23 | it 'contains "pattern"' do 24 | is_expected.to include('pattern' => '/v1/some/path$') 25 | end 26 | 27 | it 'contains "http_method"' do 28 | is_expected.to include('http_method' => verb.upcase) 29 | end 30 | 31 | it 'contains "delta"' do 32 | is_expected.to include('delta' => 1) 33 | end 34 | 35 | it 'contains "metric_id"' do 36 | is_expected.to include('metric_id' => metric_id) 37 | end 38 | 39 | context 'base path ends with /' do 40 | let(:public_base_path) { '/v1/' } 41 | it 'pattern removes last /' do 42 | is_expected.to include('pattern' => '/v1/some/path$') 43 | end 44 | end 45 | 46 | context 'with prefix_matching enabled' do 47 | let(:prefix_matching) { true } 48 | it 'contains pattern without "$" at the end' do 49 | is_expected.to include('pattern' => '/v1/some/path') 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/unit/commands/import_command/openapi/method_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'OpenAPI Method' do 2 | class OpenAPIMethodClass 3 | include ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::Method 4 | 5 | attr_reader :operation 6 | 7 | def initialize(operation) 8 | @operation = operation 9 | end 10 | end 11 | 12 | context '#method' do 13 | let(:operation_id) { 'Some Operation ID' } 14 | let(:operation) { { operation_id: operation_id } } 15 | subject { OpenAPIMethodClass.new(operation).method } 16 | 17 | it 'contains "friendly_name"' do 18 | is_expected.to include('friendly_name' => operation_id) 19 | end 20 | 21 | it 'contains "system_name"' do 22 | is_expected.to include('system_name') 23 | end 24 | 25 | it '"system_name" is sanitized' do 26 | is_expected.to include('system_name' => 'some_operation_id') 27 | end 28 | end 29 | 30 | context 'operation id not available' do 31 | let(:operation) { { verb: 'get', path: '/pet/{petId}' } } 32 | 33 | subject { OpenAPIMethodClass.new(operation).method } 34 | 35 | it 'contains "friendly_name"' do 36 | is_expected.to include('friendly_name' => 'getpetpetId') 37 | end 38 | 39 | it 'contains "system_name"' do 40 | is_expected.to include('system_name' => 'getpetpetid') 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/unit/commands/policies_command/export_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::PoliciesCommand::ExportSubcommand do 2 | include_context :temp_dir 3 | 4 | context '#run' do 5 | let(:remote) { instance_double(ThreeScale::API::Client) } 6 | let(:remote_name) { 'myremote' } 7 | let(:service_ref) { 1 } 8 | let(:arguments) { { remote: remote_name, service_ref: service_ref.to_s } } 9 | let(:output_file) {} 10 | let(:options) { { file: output_file } } 11 | let(:policy1) do 12 | { 13 | 'name' => 'apicast', 14 | 'version' => 'builtin', 15 | 'configuration' => {}, 16 | 'enabled' => true 17 | } 18 | end 19 | let(:policy2) do 20 | { 21 | 'name' => 'content_caching', 22 | 'version' => 'builtin', 23 | 'configuration' => {}, 24 | 'enabled' => true 25 | } 26 | end 27 | let(:policy_chain) { [policy1, policy2] } 28 | let(:svc_a_attrs) { { 'id' => service_ref } } 29 | 30 | subject { described_class.new(options, arguments, nil) } 31 | 32 | before :example do 33 | expect(subject).to receive(:threescale_client).with(remote_name).and_return(remote) 34 | expect(remote).to receive(:show_service).and_return(svc_a_attrs) 35 | expect(remote).to receive(:show_policies).and_return(policy_chain) 36 | end 37 | 38 | context 'when file selected' do 39 | let(:output_file) { tmp_dir.join('policies.yaml') } 40 | 41 | it 'content is written to the file' do 42 | subject.run 43 | expect(output_file.read.size).to be_positive 44 | end 45 | 46 | it 'exports product policy chain' do 47 | subject.run 48 | exported_policies = YAML.safe_load(output_file.read) 49 | expect(exported_policies).to include(policy1, policy2) 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/unit/commands/policies_command/import_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::PoliciesCommand::ImportSubcommand do 2 | include_context :temp_dir 3 | 4 | context '#run' do 5 | let(:remote) { instance_double(ThreeScale::API::Client) } 6 | let(:remote_name) { 'myremote' } 7 | let(:service_ref) { 1 } 8 | let(:arguments) { { remote: remote_name, service_ref: service_ref.to_s } } 9 | let(:input_file) {} 10 | let(:options) { { file: input_file } } 11 | let(:policy1) do 12 | { 13 | 'name' => 'apicast', 14 | 'version' => 'builtin', 15 | 'configuration' => {}, 16 | 'enabled' => true 17 | } 18 | end 19 | let(:policy2) do 20 | { 21 | 'name' => 'content_caching', 22 | 'version' => 'builtin', 23 | 'configuration' => {}, 24 | 'enabled' => true 25 | } 26 | end 27 | let(:policy_chain) { [policy1, policy2] } 28 | let(:svc_a_attrs) { { 'id' => service_ref } } 29 | 30 | subject { described_class.new(options, arguments, nil) } 31 | 32 | before :example do 33 | expect(subject).to receive(:threescale_client).with(remote_name).and_return(remote) 34 | expect(remote).to receive(:show_service).and_return(svc_a_attrs) 35 | end 36 | 37 | context 'when file selected' do 38 | let(:input_file) { tmp_dir.join('policies.yaml').tap { |policies_file| policies_file.write(policy_chain.to_yaml) } } 39 | 40 | it 'imports product policy chain' do 41 | expect(remote).to receive(:update_policies).with(service_ref, hash_including('policies_config' => policy_chain)) 42 | 43 | subject.run 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/unit/commands/product_command/copy_command/delete_target_backend_usages_task_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ProductCommand::CopyCommand::DeleteExistingTargetBackendUsagesTask do 2 | let(:source) { instance_double(ThreeScaleToolbox::Entities::Service, 'source') } 3 | let(:target) { instance_double(ThreeScaleToolbox::Entities::Service, 'target') } 4 | let(:source_usage_01) do 5 | ThreeScaleToolbox::Entities::BackendUsage.new( 6 | id: 1, product: source, attrs: { 'backend_id' => 1, 'path' => '/v1' } 7 | ) 8 | end 9 | let(:source_usage_02) do 10 | ThreeScaleToolbox::Entities::BackendUsage.new( 11 | id: 2, product: source, attrs: { 'backend_id' => 2, 'path' => '/v2' } 12 | ) 13 | end 14 | let(:target_usage_01) do 15 | ThreeScaleToolbox::Entities::BackendUsage.new( 16 | id: 100, product: target, attrs: { 'backend_id' => 10, 'path' => '/v1' } 17 | ) 18 | end 19 | let(:target_usage_02) do 20 | ThreeScaleToolbox::Entities::BackendUsage.new( 21 | id: 101, product: target, attrs: { 'backend_id' => 11, 'path' => '/somethinglese' } 22 | ) 23 | end 24 | let(:source_list) { [source_usage_01, source_usage_02] } 25 | let(:target_list) { [target_usage_01, target_usage_02] } 26 | let(:context) { { target: target, source: source } } 27 | subject { described_class.new(context) } 28 | 29 | context '#call' do 30 | before :each do 31 | allow(source).to receive(:remote) 32 | allow(target).to receive(:remote) 33 | expect(source).to receive(:backend_usage_list).and_return(source_list) 34 | expect(target).to receive(:backend_usage_list).and_return(target_list) 35 | end 36 | 37 | it 'only target_usage_01 is deleted' do 38 | expect(target_usage_01).to receive(:delete) 39 | subject.call 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/unit/commands/proxy_command/deploy_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ProxyCommand::DeploySubcommand do 2 | context '#run' do 3 | let(:remote) { instance_double(ThreeScale::API::Client) } 4 | let(:remote_name) { 'myremote' } 5 | let(:service_ref) { 'myservice' } 6 | let(:service_id) { 1 } 7 | let(:arguments) { {remote: remote_name, service_ref: service_ref} } 8 | let(:options) { {} } 9 | let(:proxy_attrs) { { 'id' => '1' } } 10 | let(:pretty_printed_proxy) { JSON.pretty_generate(proxy_attrs) + "\n" } 11 | let(:service_class) { class_double(ThreeScaleToolbox::Entities::Service).as_stubbed_const } 12 | let(:service) { instance_double(ThreeScaleToolbox::Entities::Service) } 13 | 14 | subject { described_class.new(options, arguments, nil) } 15 | 16 | before :example do 17 | expect(subject).to receive(:threescale_client).with(remote_name).and_return(remote) 18 | expect(service_class).to receive(:find).with(remote: remote, ref: service_ref).and_return(service) 19 | expect(service).to receive(:proxy_deploy).and_return(proxy_attrs) 20 | end 21 | 22 | it 'promotes APIcast configuration to staging' do 23 | expect { subject.run }.to output(pretty_printed_proxy).to_stdout 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/unit/commands/proxy_command/show_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ProxyCommand::ShowSubcommand do 2 | context '#run' do 3 | let(:remote) { instance_double(ThreeScale::API::Client) } 4 | let(:remote_name) { 'myremote' } 5 | let(:service_ref) { 'myservice' } 6 | let(:service_id) { 1 } 7 | let(:arguments) { {remote: remote_name, service_ref: service_ref} } 8 | let(:options) { {} } 9 | let(:proxy_attrs) { { 'id' => '1' } } 10 | let(:pretty_printed_proxy) { JSON.pretty_generate(proxy_attrs) + "\n" } 11 | let(:service_class) { class_double(ThreeScaleToolbox::Entities::Service).as_stubbed_const } 12 | let(:service) { instance_double(ThreeScaleToolbox::Entities::Service) } 13 | 14 | subject { described_class.new(options, arguments, nil) } 15 | 16 | before :example do 17 | expect(subject).to receive(:threescale_client).with(remote_name).and_return(remote) 18 | expect(service_class).to receive(:find).with(remote: remote, ref: service_ref).and_return(service) 19 | expect(service).to receive(:proxy).and_return(proxy_attrs) 20 | end 21 | 22 | it 'APIcast configuration is shown' do 23 | expect { subject.run }.to output(pretty_printed_proxy).to_stdout 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/unit/commands/proxy_config_command/deploy_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ProxyConfigCommand::DeploySubcommand do 2 | context '#run' do 3 | let(:remote) { instance_double(ThreeScale::API::Client) } 4 | let(:remote_name) { 'myremote' } 5 | let(:service_ref) { 'myservice' } 6 | let(:service_id) { 1 } 7 | let(:arguments) { {remote: remote_name, service_ref: service_ref} } 8 | let(:options) { {} } 9 | let(:proxy_attrs) { { 'id' => '1' } } 10 | let(:pretty_printed_proxy) { JSON.pretty_generate(proxy_attrs) + "\n" } 11 | let(:service_class) { class_double(ThreeScaleToolbox::Entities::Service).as_stubbed_const } 12 | let(:service) { instance_double(ThreeScaleToolbox::Entities::Service) } 13 | 14 | subject { described_class.new(options, arguments, nil) } 15 | 16 | before :example do 17 | expect(subject).to receive(:threescale_client).with(remote_name).and_return(remote) 18 | expect(service_class).to receive(:find).with(remote: remote, ref: service_ref).and_return(service) 19 | expect(service).to receive(:proxy_deploy).and_return(proxy_attrs) 20 | end 21 | 22 | it 'exports proxy config for all products' do 23 | expect { subject.run }.to output(pretty_printed_proxy).to_stdout 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/unit/commands/proxy_config_command/export_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ProxyConfigCommand::Export::ExportSubcommand do 2 | context '#run' do 3 | let(:remote) { instance_double(ThreeScale::API::Client) } 4 | let(:proxy_config_env) { 'production' } 5 | let(:remote_name) { 'myremote' } 6 | let(:arguments) { { remote: remote_name } } 7 | let(:options) { { environment: proxy_config_env } } 8 | let(:svc_a_attrs) { { 'id' => '1' } } 9 | let(:svc_b_attrs) { { 'id' => '2' } } 10 | let(:content_a) { { 'some_attr' => 'A' } } 11 | let(:content_b) { { 'some_attr' => 'B' } } 12 | let(:proxy_conf_a) { { 'id' => '1', 'version' => 23, 'content' => content_a } } 13 | let(:proxy_conf_b) { { 'id' => '2', 'version' => 13, 'content' => content_b } } 14 | let(:service_list_attrs) { [svc_a_attrs, svc_b_attrs] } 15 | let(:pretty_printed_configs) { JSON.pretty_generate('services' => [content_a, content_b]) + "\n" } 16 | 17 | let(:service_class) { class_double(ThreeScaleToolbox::Entities::Service).as_stubbed_const } 18 | let(:proxy_config_class) { class_double(ThreeScaleToolbox::Entities::ProxyConfig).as_stubbed_const } 19 | let(:proxy_config) { instance_double(ThreeScaleToolbox::Entities::ProxyConfig) } 20 | let(:service) { instance_double(ThreeScaleToolbox::Entities::Service) } 21 | 22 | subject { described_class.new(options, arguments, nil) } 23 | 24 | before :example do 25 | expect(subject).to receive(:threescale_client).with(remote_name).and_return(remote) 26 | expect(remote).to receive(:list_services).and_return(service_list_attrs) 27 | expect(remote).to receive(:proxy_config_latest).with(1, proxy_config_env).and_return(proxy_conf_a) 28 | expect(remote).to receive(:proxy_config_latest).with(2, proxy_config_env).and_return(proxy_conf_b) 29 | end 30 | 31 | it 'exports proxy config for all products' do 32 | expect { subject.run }.to output(pretty_printed_configs).to_stdout 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/commands/service_command/copy_command/copy_policies_task_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ServiceCommand::CopyCommand::CopyPoliciesTask do 2 | context '#call' do 3 | let(:source) { instance_double('ThreeScaleToolbox::Entities::Service', 'source') } 4 | let(:target) { instance_double('ThreeScaleToolbox::Entities::Service', 'target') } 5 | let(:source_policies) { [] } 6 | 7 | subject { described_class.new(source: source, target: target) } 8 | 9 | it 'does not call create_method method' do 10 | expect(source).to receive(:policies).and_return(source_policies) 11 | expect(target).to receive(:update_policies).with('policies_config' => source_policies) 12 | 13 | subject.call 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/commands/service_command/copy_command/destroy_mapping_rules_task_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ServiceCommand::CopyCommand::DestroyMappingRulesTask do 2 | context '#call' do 3 | let(:target) { instance_double('ThreeScaleToolbox::Entities::Service', 'target') } 4 | let(:base_context) { { target: target, logger: Logger.new('/dev/null') } } 5 | let(:task_context) { base_context } 6 | subject { described_class.new(task_context) } 7 | 8 | context 'delete_mapping_rules flag false' do 9 | let(:task_context) { base_context.merge(delete_mapping_rules: false) } 10 | it 'no op' do 11 | # Run 12 | subject.call 13 | end 14 | end 15 | 16 | context 'several mapping rules available' do 17 | let(:task_context) { base_context.merge(delete_mapping_rules: true) } 18 | let(:n_rules) { 10 } 19 | let(:target_mapping_rules) do 20 | Array.new(n_rules) do |_| 21 | instance_double('ThreeScaleToolbox::Entities::MappingRule') 22 | end 23 | end 24 | 25 | it 'it calls delete_mapping_rule method on each rule' do 26 | expect(target).to receive(:mapping_rules).and_return(target_mapping_rules) 27 | expect(target_mapping_rules.size).to be > 0 28 | target_mapping_rules.each do |mapping_rule| 29 | expect(mapping_rule).to receive(:delete) 30 | end 31 | 32 | # Run 33 | subject.call 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/unit/commands/service_command/delete_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ServiceCommand::DeleteSubcommand do 2 | include_context :random_name 3 | 4 | context '#run' do 5 | let(:remote) { instance_double(ThreeScale::API::Client, 'remote') } 6 | let(:service_class) { class_double(ThreeScaleToolbox::Entities::Service).as_stubbed_const } 7 | let(:service) { instance_double(ThreeScaleToolbox::Entities::Service) } 8 | let(:remote_name) { "myremote" } 9 | let(:options) {} 10 | 11 | subject { described_class.new(options, arguments, nil) } 12 | 13 | before :example do 14 | expect(subject).to receive(:threescale_client).with(remote_name).and_return(remote) 15 | end 16 | 17 | context "when the service does not exists" do 18 | let(:service_ref) { "unexistingservice" } 19 | let(:arguments) { { remote: remote_name, service_id_or_system_name: service_ref } } 20 | 21 | it 'an error is raised' do 22 | expect(service_class).to receive(:find).with(remote: remote, ref: service_ref).and_return(nil) 23 | expect do 24 | subject.run 25 | end.to raise_error(ThreeScaleToolbox::Error, /Service.*does not exist/) 26 | end 27 | end 28 | 29 | context "when a service exists" do 30 | let(:service_ref) { "existingservice" } 31 | let(:arguments) { { remote: remote_name, service_id_or_system_name: service_ref } } 32 | 33 | it 'is removed' do 34 | svc_id = "3" 35 | expect(service).to receive(:delete).and_return(true) 36 | expect(service).to receive(:id).and_return(svc_id) 37 | expect(service_class).to receive(:find).with(remote: remote, ref: service_ref).and_return(service) 38 | expect do 39 | subject.run 40 | end.to output(/.*Service with id: #{svc_id} deleted.*/).to_stdout 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/unit/commands/service_command/list_command_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::Commands::ServiceCommand::ListSubcommand do 2 | context '#run' do 3 | let(:remote) { instance_double(ThreeScale::API::Client, 'remote') } 4 | let(:remote_name) { "myremote" } 5 | 6 | let(:options) { {} } 7 | let(:arguments) { { remote: remote_name } } 8 | 9 | subject { described_class.new(options, arguments, nil) } 10 | 11 | before :example do 12 | expect(subject).to receive(:threescale_client).with(remote_name).and_return(remote) 13 | end 14 | 15 | it 'when no services are present only the result header is printed' do 16 | expect(remote).to receive(:list_services).and_return([]) 17 | expect { subject.run }.to output("ID\tNAME\tSYSTEM_NAME\n").to_stdout 18 | end 19 | 20 | it 'when services are present those are printed' do 21 | expect(remote).to receive(:list_services).and_return( 22 | [ 23 | { "id" => 1, "name" => "name1", "system_name" => "sysname1" }, 24 | { "id" => 2, "name" => "name2", "system_name" => "sysname2" }, 25 | ] 26 | ) 27 | expect { subject.run }.to output("ID\tNAME\tSYSTEM_NAME\n1\tname1\tsysname1\n2\tname2\tsysname2\n").to_stdout 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/unit/error_handler_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::CLI::ErrorHandler do 2 | include_context :temp_dir 3 | 4 | context '#error_watchdog' do 5 | def raise_runtime_error 6 | raise 'some error' 7 | end 8 | 9 | def raise_toolbox_error 10 | raise ThreeScaleToolbox::Error, 'some error' 11 | end 12 | 13 | context 'raises expected error' do 14 | it 'error is shown on stderr' do 15 | Dir.chdir(tmp_dir) do 16 | expect do 17 | subject.error_watchdog { raise_toolbox_error } 18 | end.to output(/some error/).to_stderr 19 | expect(File).not_to exist('crash.log') 20 | end 21 | end 22 | 23 | it 'returns error' do 24 | expect( 25 | subject.error_watchdog { raise_toolbox_error } 26 | ).to be 27 | end 28 | end 29 | 30 | context 'raises unexpected error' do 31 | it 'crash.log is generated' do 32 | Dir.chdir(tmp_dir) do 33 | expect do 34 | subject.error_watchdog { raise_runtime_error } 35 | end.to output(/some error/).to_stderr 36 | expect(File).to exist('crash.log') 37 | end 38 | end 39 | 40 | it 'returns error' do 41 | expect( 42 | subject.error_watchdog { raise_runtime_error } 43 | ).to be 44 | end 45 | end 46 | 47 | context 'Does not raise error' do 48 | it 'returns true' do 49 | expect(subject.error_watchdog {}).to be_nil 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/unit/plugin_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Plugin command' do 2 | include_context :temp_dir 3 | include_context :plugin 4 | include_context :random_name 5 | 6 | around(:each) do |example| 7 | $LOAD_PATH.unshift(tmp_dir) unless $LOAD_PATH.include?(tmp_dir) 8 | example.run 9 | $LOAD_PATH.delete(tmp_dir) 10 | end 11 | 12 | it 'is not loaded when not in load path' do 13 | expect do 14 | ThreeScaleToolbox::CLI.run(%w[simple]) 15 | end.to output(/unknown command/).to_stderr.and raise_error(SystemExit) do |e| 16 | expect(e.status).to eq 1 17 | expect(e.success?).to be_falsey 18 | end 19 | end 20 | 21 | it 'is loaded when expected' do 22 | name = random_lowercase_name 23 | plugin = get_plugin_content(name.capitalize, name) 24 | tmp_dir.join('3scale_toolbox_plugin.rb').write(plugin) 25 | expect do 26 | ThreeScaleToolbox::CLI.run([name]) 27 | end.to output("this is #{name} command\n").to_stdout 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/unit/proxy_logger_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThreeScaleToolbox::ProxyLogger do 2 | class MyTestObject 3 | def method01(_param01) 4 | 'result01' 5 | end 6 | end 7 | 8 | let(:proxied_object) { MyTestObject.new } 9 | subject { described_class.new(proxied_object) } 10 | 11 | it 'method01 exists' do 12 | expect(subject.method01('some_param')).to eq('result01') 13 | end 14 | 15 | it 'method01 method can be obtained from :method' do 16 | expect(subject.method(:method01).to_s).to eq(MyTestObject.new.method(:method01).to_s) 17 | end 18 | 19 | it 'undefined method02 raises method not found' do 20 | expect { subject.method02 }.to raise_error(NoMethodError) 21 | end 22 | 23 | it 'undefined method02 does not exist' do 24 | expect(subject.respond_to?(:method02)).to be_falsey 25 | end 26 | 27 | it 'proxy object class defined to be proxied object class' do 28 | expect(subject.class).to be(MyTestObject) 29 | end 30 | 31 | it 'method args in output' do 32 | expect do 33 | subject.method01('some_param') 34 | end.to output(/args \|\["some_param"\]\|/).to_stderr 35 | end 36 | 37 | it 'method return values in output' do 38 | expect do 39 | subject.method01('') 40 | end.to output(/response \|"result01"\|/).to_stderr 41 | end 42 | end 43 | --------------------------------------------------------------------------------