lib/service/api/insightvm.rb in avs-0.1.0 vs lib/service/api/insightvm.rb in avs-0.1.1
- old
+ new
@@ -1,159 +1,189 @@
-#!/usr/bin/env ruby
# frozen_string_literal: true
-require 'net/http'
+require 'typhoeus'
require 'json'
-require 'uri'
require 'base64'
-require 'openssl'
require_relative '../../domain/api'
+# InsightVMApi class provides an interface for interacting with the InsightVM API.
+# It handles request building, and response parsing for various API endpoints.
+#
+# @attr_reader [String] base_url The base URL for the InsightVM API
+# @attr_reader [String] base_auth The Basic Authentication string used for requests
+#
+# @example Initializing the API client
+# api = InsightVMApi.new(
+# base_url: 'https://your-insightvm-instance.com',
+# username: 'your_username',
+# password: 'your_password'
+# )
+#
+# @example Fetching all sites
+# api.fetch_all('/sites') do |site|
+# puts site['name']
+# end
+#
+# @example Getting a specific site
+# site = api.get('/sites/1')
+# puts site.inspect
class InsightVMApi
- attr_reader :http, :base_auth, :base_url
+ attr_reader :base_url, :base_auth
- # Fetchs all resources
+ # Initializes a new InsightVMApi instance.
#
- # @param [String]
- # @param [Hash] optional parameters: page, size, type, name, sort
+ # @param base_url [String] The base URL for the InsightVM API
+ # @param username [String] The username for API authentication
+ # @param password [String] The password for API authentication
+ def initialize(base_url:, username:, password:)
+ @base_url = base_url
+ @base_auth = ['Basic', Base64.strict_encode64("#{username}:#{password}")].join(' ')
+ @options = {
+ headers: {
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
+ 'Authorization' => @base_auth
+ }
+ }
+ @cached_tags = nil
+ @shared_credentials = nil
+ end
+
+ # Fetches all resources from a paginated endpoint.
+ #
+ # @param endpoint [String] The API endpoint to fetch resources from
+ # @param opts [Hash] Additional options for the request (e.g., filters)
+ # @yield [resource] Gives each resource to the block
def fetch_all(endpoint, opts = {}, &block)
params = {
page: 0,
- size: 100,
- read_timeout: 5
- }.merge opts
+ size: 100
+ }.merge(opts)
+
loop do
- full_url = @base_url.dup
- full_url.path += endpoint
- full_url.query = URI.encode_www_form(
- params
- )
- request = Net::HTTP::Get.new(full_url)
- request['Authorization'] = @base_auth
- @http.read_timeout = params[:read_timeout]
- response = @http.request(request)
- unless response.is_a?(Net::HTTPSuccess)
- puts "Error with code #{response.code}"
- break
- end
+ response = get("#{endpoint}?#{URI.encode_www_form(params)}")
+ break if response.is_a?(Hash) && response[:error]
- json_response = JSON.parse(response.body)
- resources = json_response['resources']
+ resources = response['resources']
break if resources.nil?
- resources.each(&block) # equivalent to resources.each {|resource| yield resource}
+ resources.each(&block)
- # Check if this is the last page
- current_page = json_response['page']&.[]('number') || 0
- pages = json_response['page']&.[]('totalPages') || 0
- break if params[:page] >= pages
- break if current_page + 1 >= pages
+ current_page = response['page']&.[]('number') || 0
+ pages = response['page']&.[]('totalPages') || 0
+ break if params[:page] >= pages || current_page + 1 >= pages
params[:page] += 1
end
- rescue Net::ReadTimeout
- opts[:read_timeout] = 2 * params[:read_timeout] || 30
- raise 'Fail after multiple attempts' if opts[:read_timeout] > 300
+ end
- puts "Increase the timeout to #{opts[:read_timeout]}"
- fetch_all(endpoint, opts, &block)
+ # Performs a GET request to the specified path.
+ #
+ # @param path [String] The API path to send the GET request to
+ # @param params [Hash] Query parameters to include in the request
+ # @return [Hash] The parsed JSON response or an error hash
+ def get(path, params = {})
+ run_request(:get, path, params:)
end
- def initialize(base_url, username, password)
- @base_url = URI(base_url)
- @http = Net::HTTP.new(@base_url.host, @base_url.port)
- @http.use_ssl = true
- @http.verify_mode = OpenSSL::SSL::VERIFY_NONE # Reminder: Adjust for production use
- @base_auth = ['Basic', Base64.strict_encode64("#{username}:#{password}")].join(' ')
+ # Performs a POST request to the specified endpoint.
+ #
+ # @param endpoint [String] The API endpoint to send the POST request to
+ # @param body [Hash] The request body to be sent as JSON
+ # @return [Hash] The parsed JSON response or an error hash
+ def post(endpoint, body)
+ run_request(:post, endpoint, body: body.to_json)
end
- private
+ # Performs a DELETE request to the specified endpoint.
+ #
+ # @param endpoint [String] The base API endpoint
+ # @param id [String, Integer] The ID of the resource to delete
+ # @param attempts [Integer] The number of delete attempts (for internal use)
+ def delete(endpoint, id, _attempts = 0)
+ response = run_request(:delete, "#{endpoint}/#{id}")
- # return the response.body.to_json if Success
- # else response,message and response.status if failure
- def post(endpoint, params)
- # Construct the request
- uri = URI("#{@base_url}#{endpoint}")
- request = Net::HTTP::Post.new(uri)
- request['Content-Type'] = 'application/json'
- request['Authorization'] = @base_auth
- request.body = params.to_json
- # Send the request
- response = @http.request(request)
-
- if response.is_a?(Net::HTTPSuccess)
- # puts 'Success!'
- # You can parse the response body if needed
- JSON.parse(response.body)
+ if response.is_a?(Hash) && response[:error]
+ puts "Error deleting #{endpoint}/#{id}: #{response[:error]}"
else
- puts "Error with status code: #{response.code}, Response body: #{response.body}"
- nil
- end
- end
-
- def delete(endpoint, id, attempts = 0)
- uri = URI("#{@base_url}#{endpoint}/#{id}")
- request = Net::HTTP::Delete.new(uri)
- request['Content-Type'] = 'application/json'
- request['Authorization'] = @base_auth
- # Send the requesbody
- response = @http.request(request)
- case response
- when Net::HTTPSuccess
puts "#{endpoint}/#{id} deleted successfully."
- when Net::ReadTimeout
- delete(endpoint, id, attempts + 1) if attempts < 5
- else
- puts "Error deleting #{endpoint}/#{id} Status code: #{response.code}, Response body: #{response.body}"
end
end
+ # Performs a PATCH request to the specified endpoint.
+ #
+ # @param endpoint [String] The API endpoint to send the PATCH request to
+ # @param body [Hash] The request body to be sent as JSON
def patch(endpoint, body)
- # Construct the request
- uri = URI("#{@base_url}#{endpoint}")
- request = Net::HTTP::Patch.new(uri)
- request['Content-Type'] = 'application/json'
- request['Authorization'] = @base_auth
- request.body = JSON.generate(body)
- # Send the requesbody
- response = @http.request(request)
+ response = run_request(:patch, endpoint, body: body.to_json)
+ return unless response.is_a?(Hash) && response[:error]
- return if response.is_a?(Net::HTTPSuccess)
-
- puts "Error PATCH #{endpoint}. Status code: #{response.code}\n Response body: #{response.body}"
+ puts "Error PATCH #{endpoint}: #{response[:error]}"
end
+ # Performs a PUT request to the specified endpoint.
+ #
+ # @param endpoint [String] The API endpoint to send the PUT request to
+ # @param body [Hash] The request body to be sent as JSON
def put(endpoint, body)
- # Construct the request
- uri = URI("#{@base_url}#{endpoint}")
- request = Net::HTTP::Put.new(uri)
- request['Content-Type'] = 'application/json'
- request['Authorization'] = @base_auth
- request.body = JSON.generate(body)
+ json = JSON.generate(body)
+ response = run_request(:put, endpoint, body: json)
+ return response unless response.is_a?(Hash) && response[:error]
- # Send the requesbody
- response = @http.request(request)
-
- return if response.is_a?(Net::HTTPSuccess)
-
- puts "Error PUT #{endpoint}. Status code: #{response.code}, Response body: #{response.body}"
+ puts "Error PUT #{endpoint}: #{response[:error]}"
end
+ # Fetches data from an endpoint with retry logic.
+ #
+ # @param endpoint [String] The API endpoint to fetch data from
+ # @param attempts [Integer] The number of fetch attempts (for internal use)
+ # @yield [response] Gives the successful response to the block
+ # @raise [RuntimeError] If the maximum number of retries is exceeded
def fetch(endpoint, attempts = 0)
max_retries = 5
- uri = URI("#{@base_url}#{endpoint}")
- request = Net::HTTP::Get.new(uri)
- request['Authorization'] = @base_auth
- response = @http.request(request)
- raise "HTTP Error #{response.code} #{response.message}" unless response.is_a?(Net::HTTPSuccess)
+ response = get(endpoint)
- yield JSON.parse(response.body)
- rescue Net::ReadTimeout => e
- puts "Attempts #{attempts}"
- raise "Network error after #{max_retries} #{e.message}" unless attempts < max_retries
+ if response.is_a?(Hash) && response[:error]
+ raise "Network error after #{max_retries} attempts: #{response[:error]}" unless attempts < max_retries
- sleep 2**attempts
- fetch(endpoint, attempts + 1)
- else
- # TODO
+ sleep 2**attempts
+ fetch(endpoint, attempts + 1)
+
+ else
+ yield response
+ end
+ end
+
+ private
+
+ # Runs an HTTP request using Typhoeus.
+ #
+ # @param method [Symbol] The HTTP method (:get, :post, etc.)
+ # @param path [String] The API path for the request
+ # @param options [Hash] Additional options for the request
+ # @return [Hash] The parsed JSON response or an error hash
+ def run_request(method, path, options = {})
+ request = Typhoeus::Request.new(
+ "#{@base_url}#{path}",
+ method:,
+ headers: @options[:headers],
+ **options
+ )
+
+ response = request.run
+
+ if response.success?
+ JSON.parse(response.body)
+ elsif response.timed_out?
+ { error: 'Request timed out' }
+ elsif response.code.zero?
+ { error: response.return_message }
+ else
+ { error: "HTTP request failed: #{response.code}" }
+ end
+ end
+
+ def confirm_action(prompt)
+ print "#{prompt} (y/n): "
+ gets.chomp.downcase == 'y'
end
end