aho 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +34 -7
  3. data/.ruby-version +1 -1
  4. data/README.md +2 -2
  5. data/Rakefile +3 -3
  6. data/aho.gemspec +41 -0
  7. data/app/controllers/dsp/api/v1/agent_controller.rb +17 -0
  8. data/app/controllers/dsp/api/v1/base_controller.rb +32 -0
  9. data/app/controllers/dsp/api/v1/config_controller.rb +20 -0
  10. data/app/controllers/dsp/api/v1/healthcheck_controller.rb +29 -0
  11. data/app/controllers/dsp/api/v1/metrics_controller.rb +13 -0
  12. data/config/default.aho.yml +36 -0
  13. data/config/puma.rb +5 -0
  14. data/config/routes.rb +10 -0
  15. data/config.ru +5 -0
  16. data/exe/aho +7 -3
  17. data/lib/aho/configuration.rb +43 -0
  18. data/lib/aho/errors.rb +19 -0
  19. data/lib/aho/metrics/active.rb +58 -0
  20. data/lib/aho/metrics/ignore.rb +23 -0
  21. data/lib/aho/metrics_aggregator.rb +29 -0
  22. data/lib/aho/metrics_builder.rb +14 -0
  23. data/lib/aho/metrics_formatter.rb +7 -0
  24. data/lib/aho/metrics_log_device.rb +10 -0
  25. data/lib/aho/metrics_logger/period.rb +27 -0
  26. data/lib/aho/metrics_logger.rb +24 -0
  27. data/lib/aho/schema/parser.rb +45 -0
  28. data/lib/aho/schema/validator.rb +55 -0
  29. data/lib/aho/version.rb +1 -1
  30. data/lib/aho/web_server/application.rb +44 -0
  31. data/lib/aho/web_server/router.rb +34 -0
  32. data/lib/aho.rb +50 -7
  33. data/lib/extensions/dot_hash.rb +29 -0
  34. data/lib/extensions/hash/deep_mergeable.rb +24 -0
  35. data/lib/extensions/hash.rb +43 -0
  36. data/openapi.yml +386 -0
  37. metadata +100 -18
  38. data/.idea/.gitignore +0 -8
  39. data/.idea/aho.iml +0 -77
  40. data/.idea/misc.xml +0 -4
  41. data/.idea/modules.xml +0 -8
  42. data/.idea/vcs.xml +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68395a8675b36a6ef2e9f76ac6c42a5ef69fa08a27bb7da3f1ffca0ee6b3c0dc
4
- data.tar.gz: 65a69f4031fea00f45ccb12e500430b2aada64404934405383c8fc5be7243331
3
+ metadata.gz: d93a28179434feaf3200bce31c7d59f485abc0f022b96cb9482f32a1c44cef3a
4
+ data.tar.gz: 9835711037d2ea542097648aa41217a4c57ab821afc388df25fad268d20b94e2
5
5
  SHA512:
6
- metadata.gz: f22afb4971fb121cf7f9c0a9deb72e032c8867feb053519f75b51a2a2f6ac2b3cfb9f083b0af60ac36744412929ec81502c6955785d86a3c95b9d866d7ac7627
7
- data.tar.gz: 0f9ce4932226b38aa32d3ae0419352d4839f939ff81ebf6de20757d32613f566b9e2e082e5e98ae67eac6738554cb3d9d32012722c14d01b3d537463cf420f32
6
+ metadata.gz: 1c52ab0b387257371e8f3cde64f9aed5ec71bcdb5abec1c726534a5e63b4f80c837ec121fcf58508b9cf022bf02bdfc26cc83bbd33f7d3061a6b1e5702c26b30
7
+ data.tar.gz: 88515e06c4d939753d121682f1db5d8c3ba92b9e3abe1bd068390fdbf7a706a8e3256a7d9fb41c2ebd6d618586a3602ee73d2bf0fba9cbd262140234194fea1f
data/.rubocop.yml CHANGED
@@ -1,13 +1,40 @@
1
+ require:
2
+ - rubocop-rspec
3
+ - rubocop-performance
4
+ - rubocop-rake
5
+
1
6
  AllCops:
2
- TargetRubyVersion: 3.0
7
+ TargetRubyVersion: 3.4.2
8
+ NewCops: enable
9
+
10
+ Metrics/MethodLength:
11
+ Max: 15
3
12
 
4
- Style/StringLiterals:
5
- Enabled: true
6
- EnforcedStyle: double_quotes
13
+ Metrics/CyclomaticComplexity:
14
+ Max: 21
7
15
 
8
- Style/StringLiteralsInInterpolation:
9
- Enabled: true
10
- EnforcedStyle: double_quotes
16
+ Metrics/PerceivedComplexity:
17
+ Max: 21
18
+
19
+ Metrics/AbcSize:
20
+ Max: 100
11
21
 
12
22
  Layout/LineLength:
13
23
  Max: 120
24
+
25
+ Style/Documentation:
26
+ Enabled: false
27
+
28
+ RSpec/MultipleExpectations:
29
+ Enabled: false
30
+
31
+ RSpec/ExampleLength:
32
+ Exclude:
33
+ - 'spec/requests/**/*'
34
+
35
+ RSpec/MultipleMemoizedHelpers:
36
+ Exclude:
37
+ - 'spec/requests/**/*'
38
+
39
+ Naming/VariableNumber:
40
+ EnforcedStyle: snake_case
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.2.2
1
+ 3.4.2
data/README.md CHANGED
@@ -4,12 +4,12 @@ Aho — агент для сбора данных с ресурсов для б
4
4
 
5
5
  ## Установка
6
6
 
7
- Для установки агента Aho выполните команду:
7
+ Установите агента командой:
8
8
 
9
9
  $ gem install aho
10
10
 
11
11
  ## Использование
12
12
 
13
- Для проверки установки выполните `aho`, в ответ должен быть ответ «Aho!»
13
+ Для запуска выполните `aho`, в ответ запустится Puma сервер:
14
14
 
15
15
  $ aho
data/Rakefile CHANGED
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- require "rubocop/rake_task"
8
+ require 'rubocop/rake_task'
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
data/aho.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/aho/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'aho'
7
+ spec.version = Aho::VERSION
8
+ spec.authors = ['Panasenkov A.']
9
+ spec.email = ['aapanasenkov@gmail.com']
10
+
11
+ spec.summary = 'Aho agent for billing, monitoring and audit data from resources.'
12
+ spec.description = 'Aho agent is responsible for collecting resource usage data of VM instances on \
13
+ individual Compute nodes within an Datamart Platform Studio deployment.'
14
+ spec.homepage = 'https://datamart.ru/aho'
15
+ spec.required_ruby_version = '>= 3.4.2'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://gitlab.datamart.ru/datamart/aho'
19
+ spec.metadata['changelog_uri'] = 'https://gitlab.datamart.ru/datamart/aho/CHANGELOG.md'
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (File.expand_path(f) == __FILE__) ||
26
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
27
+ end
28
+ end
29
+ spec.bindir = 'exe'
30
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ['lib']
32
+
33
+ # For more information and examples about making a new gem, check out our
34
+ # guide at: https://bundler.io/guides/creating_gem.html
35
+ spec.metadata['rubygems_mfa_required'] = 'true'
36
+
37
+ spec.add_dependency 'puma', '~> 6.4', '>= 6.4.3'
38
+ spec.add_dependency 'rack', '~> 3.1', '>= 3.1.8'
39
+ spec.add_dependency 'rackup', '~> 2.1'
40
+ spec.add_dependency 'rake', '~> 13.2', '>= 13.2.1'
41
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsp
4
+ module Api
5
+ module V1
6
+ class AgentController < BaseController
7
+ def restart
8
+ [200, {}, ['restart']]
9
+ end
10
+
11
+ def destroy
12
+ [200, {}, ['destroy']]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsp
4
+ module Api
5
+ module V1
6
+ class BaseController
7
+ attr_reader :env, :request, :path, :method
8
+
9
+ def initialize(env)
10
+ @env = env
11
+ @request = Rack::Request.new(env)
12
+ @path = request.path
13
+ @method = request.request_method
14
+ end
15
+
16
+ private
17
+
18
+ def respond(response, status: 200)
19
+ [status, { 'Content-Type' => 'application/json' }, [response.to_json]]
20
+ end
21
+
22
+ def schema
23
+ Aho.router.routes.dig(method.downcase.to_sym, path)[:schema]
24
+ end
25
+
26
+ def validate_schema!(...)
27
+ Aho::Schema::Validator.new(...).validate!
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsp
4
+ module Api
5
+ module V1
6
+ class ConfigController < BaseController
7
+ def index
8
+ respond(Aho.config.settings)
9
+ end
10
+
11
+ def create
12
+ body = JSON.parse(request.body.read)
13
+ validate_schema!(body, schema)
14
+ Aho.config.update(body)
15
+ respond({ status: 'OK', message: 'Configuration applied successfully' })
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsp
4
+ module Api
5
+ module V1
6
+ class HealthcheckController < BaseController
7
+ def index
8
+ render_up
9
+ rescue StandardError
10
+ render_down
11
+ end
12
+
13
+ private
14
+
15
+ def render_up
16
+ [200, { 'Content-Type' => 'text/html' }, [html_status(color: 'green')]]
17
+ end
18
+
19
+ def render_down
20
+ [500, { 'Content-Type' => 'text/html' }, [html_status(color: 'red')]]
21
+ end
22
+
23
+ def html_status(color:)
24
+ %(<!DOCTYPE html><html><body style="background-color: #{color}"></body></html>)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsp
4
+ module Api
5
+ module V1
6
+ class MetricsController < BaseController
7
+ def index
8
+ [200, {}, ['metrics']]
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ server_id: STUDIO_SERVER_ID
2
+ ignore_metrics: -1
3
+ life_jwt_time: 3600
4
+ scrape_interval_time: 60
5
+ enable_agent: -1
6
+
7
+ upload:
8
+ limit_time: 3600
9
+ limit_lines: 100
10
+ limit_size: 1
11
+
12
+ studio_settings:
13
+ url: http://localhost:8088/dsp/api/v1/vmagent
14
+
15
+ agent_settings:
16
+ port: 16888
17
+
18
+ storage:
19
+ limit_size: 0
20
+ limit_time: 86400
21
+ directory: /opt/dtms-billing-agent/
22
+
23
+ file:
24
+ limit_size: 1
25
+ limit_time: 86400
26
+
27
+ archive:
28
+ files_on: 0
29
+ size_on: 0
30
+ files_limit: 10
31
+ compression_level: -1
32
+
33
+ aggregation:
34
+ files_on: 0
35
+ size_on: 0
36
+ level: 1
data/config/puma.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/aho'
4
+
5
+ port Aho.config.settings.agent_settings.port
data/config/routes.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ Aho.router.instance_eval do
4
+ get '/dsp/api/v1/metrics', to: 'dsp/api/v1/metrics#index'
5
+ get '/dsp/api/v1/config', to: 'dsp/api/v1/config#index'
6
+ post '/dsp/api/v1/config', to: 'dsp/api/v1/config#create'
7
+ post '/dsp/api/v1/restart', to: 'dsp/api/v1/agent#restart'
8
+ delete '/dsp/api/v1/delete', to: 'dsp/api/v1/agent#destroy'
9
+ get '/dsp/api/v1/healthcheck', to: 'dsp/api/v1/healthcheck#index'
10
+ end
data/config.ru ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'config/routes'
4
+
5
+ run Aho::WebServer::Application.new
data/exe/aho CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "bundler/setup"
5
- require "aho"
4
+ # require 'bundler/setup'
5
+ require 'aho'
6
6
 
7
- system "echo Aho!"
7
+ root_dir = File.expand_path('..', __dir__)
8
+ rackup_file = File.join(root_dir, 'config.ru')
9
+ puma_config = File.join(root_dir, 'config', 'puma.rb')
10
+
11
+ exec "puma -C #{puma_config} #{rackup_file}"
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aho
4
+ class Configuration
5
+ DEFAULT_CONFIG_FILE_PATH = File.expand_path('../../config/default.aho.yml', __dir__)
6
+ CONFIG_FILE_PATH = File.expand_path('../../config/aho.yml', __dir__)
7
+
8
+ attr_reader :settings
9
+
10
+ def initialize
11
+ reset unless File.exist?(CONFIG_FILE_PATH)
12
+ raw_settings = YAML.load_file(CONFIG_FILE_PATH)
13
+ rich_settings = process_env_variables(raw_settings)
14
+ @settings = DotHash.new(rich_settings)
15
+ end
16
+
17
+ def update(new_settings)
18
+ @settings.deep_merge!(new_settings.stringify_keys.slice(*default_settings.keys))
19
+ File.write(CONFIG_FILE_PATH, @settings.to_h.to_yaml)
20
+ end
21
+
22
+ def reset
23
+ File.write(CONFIG_FILE_PATH, File.read(DEFAULT_CONFIG_FILE_PATH))
24
+ @settings = DotHash.new(default_settings)
25
+ end
26
+
27
+ private
28
+
29
+ def process_env_variables(settings)
30
+ settings.deep_transform_values do |value|
31
+ if value.is_a?(String) && ENV.key?(value)
32
+ ENV.fetch(value, nil)
33
+ else
34
+ value
35
+ end
36
+ end
37
+ end
38
+
39
+ def default_settings
40
+ @default_settings ||= YAML.load_file(DEFAULT_CONFIG_FILE_PATH)
41
+ end
42
+ end
43
+ end
data/lib/aho/errors.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aho
4
+ class Error < StandardError; end
5
+
6
+ module Api
7
+ class MissingFieldError < Error
8
+ def initialize(field_name)
9
+ super("Missing required field '#{field_name}'")
10
+ end
11
+ end
12
+
13
+ class InvalidTypeError < Error
14
+ def initialize(field_name, expected_type, value)
15
+ super("Invalid type for '#{field_name}'. Expected #{expected_type}, got #{value.class}")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../metrics_builder'
4
+
5
+ module Aho
6
+ module Metrics
7
+ class Active < MetricsBuilder
8
+ def metrics
9
+ {
10
+ timestamp: run('date'),
11
+ server_id: 'TODO',
12
+ event_type: 'TODO',
13
+ description: 'TODO',
14
+ crashtime: 'TODO',
15
+ uptime: run('uptime'),
16
+ cpu_core: run('nproc'),
17
+ ram_total: run("awk '/MemTotal/ {print $2}' /proc/meminfo"),
18
+ disk_total: run("lsblk -b -o SIZE | awk 'NR>1 {total+=$1} END {print total}'"),
19
+ cpu_seconds_total:,
20
+ ram_used: run("free | awk '/^Mem/ {print $3}'"),
21
+ disk_used: run("df --block-size=1 | awk 'NR>1 {used+=$3} END {print used}'"),
22
+ network_load:
23
+ }
24
+ end
25
+
26
+ private
27
+
28
+ def cpu_seconds_total
29
+ File.readlines('/proc/stat').each_with_object([]) do |line, stats|
30
+ next unless line.start_with?('cpu') && !line.start_with?('cpu ')
31
+
32
+ parts = line.split
33
+ stats << {
34
+ cpu: parts[0],
35
+ user: parts[1].to_i,
36
+ system: parts[3].to_i,
37
+ idle: parts[4].to_i
38
+ }
39
+ end
40
+ rescue Errno::ENOENT
41
+ nil
42
+ end
43
+
44
+ def network_load
45
+ File.readlines('/proc/net/dev').drop(2).each_with_object([]) do |line, stats|
46
+ parts = line.split
47
+ stats << {
48
+ device: parts[0].delete_suffix(':'),
49
+ received: parts[1].to_i,
50
+ sent: parts[9].to_i
51
+ }
52
+ end
53
+ rescue Errno::ENOENT
54
+ nil
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../metrics_builder'
4
+
5
+ module Aho
6
+ module Metrics
7
+ class Ignore < MetricsBuilder
8
+ def metrics
9
+ {
10
+ loadaverage_01: run("cat /proc/loadavg | awk '{print $1}'"),
11
+ loadaverage_05: run("cat /proc/loadavg | awk '{print $2}'"),
12
+ loadaverage_15: run("cat /proc/loadavg | awk '{print $3}'"),
13
+ tasks_total: run("top -bn1 | grep Tasks | awk '{print $2}'"),
14
+ tasks_running: run("top -bn1 | grep Tasks | awk '{print $4}'"),
15
+ disk_reads_completed_total: run("awk '{sum += $4} END {print sum}' /proc/diskstats"),
16
+ disk_writes_completed_total: run("awk '{sum += $8} END {print sum}' /proc/diskstats"),
17
+ swap_total_bytes: run("free --bytes | awk '/^Swap:/ {print $2}'"),
18
+ swap_used_bytes: run("free --bytes | awk '/^Swap:/ {print $3}'")
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'metrics/active'
4
+ require_relative 'metrics/ignore'
5
+
6
+ module Aho
7
+ class MetricsAggregator
8
+ attr_reader :metrics
9
+
10
+ def initialize
11
+ @metrics = active_metrics
12
+ @metrics.merge!(ignore_metrics) if collect_ignore_metrics?
13
+ end
14
+
15
+ private
16
+
17
+ def active_metrics
18
+ Aho::Metrics::Active.new.metrics
19
+ end
20
+
21
+ def ignore_metrics
22
+ Aho::Metrics::Ignore.new.metrics
23
+ end
24
+
25
+ def collect_ignore_metrics?
26
+ Aho.config.settings.ignore_metrics == -1
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module Aho
6
+ class MetricsBuilder
7
+ def run(command)
8
+ stdout, _stderr, status = Open3.capture3(command)
9
+ stdout.strip if status.success?
10
+ rescue Errno::ENOENT => _e
11
+ nil
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MetricsFormatter
4
+ def call(_severity, _time, _progname, msg)
5
+ "#{msg.to_json}\n"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MetricsLogDevice < Logger::LogDevice
4
+ private
5
+
6
+ # Override to prevent the default header from being written
7
+ def add_log_header(file)
8
+ # Leave this method empty to avoid adding any header
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Logger
4
+ module Period
5
+ module_function
6
+
7
+ def next_rotate_time(now, shift_age)
8
+ now + shift_age.to_i
9
+ end
10
+
11
+ def previous_period_end(now, shift_age)
12
+ # case shift_age
13
+ # when 'daily', :daily
14
+ # t = Time.mktime(now.year, now.month, now.mday) - SiD / 2
15
+ # when 'weekly', :weekly
16
+ # t = Time.mktime(now.year, now.month, now.mday) - (SiD * now.wday + SiD / 2)
17
+ # when 'monthly', :monthly
18
+ # t = Time.mktime(now.year, now.month, 1) - SiD / 2
19
+ # when 'now', 'everytime', :now, :everytime
20
+ # return now
21
+ # else
22
+ # raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime"
23
+ # end
24
+ # Time.mktime(t.year, t.month, t.mday, 23, 59, 59)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'metrics_log_device'
4
+
5
+ module Aho
6
+ class MetricsLogger < Logger
7
+ TIMESTAMP_FORMAT = '%Y-%m-%d_%H-%M-%S'
8
+
9
+ def initialize(...)
10
+ super(
11
+ MetricsLogDevice.new(log_file_path, ...),
12
+ formatter: MetricsFormatter.new,
13
+ shift_period_suffix: TIMESTAMP_FORMAT
14
+ )
15
+ end
16
+
17
+ private
18
+
19
+ def log_file_path
20
+ timestamp = Time.now.strftime(TIMESTAMP_FORMAT)
21
+ "#{Aho.logs_path}/#{timestamp}_metrics.jsonl"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aho
4
+ module Schema
5
+ class Parser
6
+ OPENAPI_FILE = 'openapi.yml'
7
+
8
+ attr_reader :method, :path, :destination, :openapi_paths
9
+
10
+ def initialize(method, path, destination)
11
+ @method = method
12
+ @path = path
13
+ @destination = destination
14
+ @openapi_paths = YAML.load_file(File.join(Aho.root_path, OPENAPI_FILE))['paths']
15
+ end
16
+
17
+ def controller
18
+ "#{destination.split('#').first.split('/').map(&:capitalize).join('::')}Controller"
19
+ end
20
+
21
+ def action
22
+ destination.split('#').last
23
+ end
24
+
25
+ def schema
26
+ remove_descriptions(openapi_paths.dig(path, method, 'requestBody', 'content', 'application/json', 'schema'))
27
+ end
28
+
29
+ private
30
+
31
+ def remove_descriptions(hash)
32
+ return unless hash.is_a?(Hash)
33
+
34
+ hash.each_value do |value|
35
+ if value.is_a?(Hash)
36
+ value.delete('description')
37
+ remove_descriptions(value)
38
+ elsif value.is_a?(Array)
39
+ value.each { |v| remove_descriptions(v) if v.is_a?(Hash) }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end