class InspecPlugins::PluginManager::CliCommand
Constants
- INSTALL_TYPE_LABELS
Public Instance Methods
install(plugin_id_arg)
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 117 def install(plugin_id_arg) if plugin_id_arg =~ /\.gem$/ # Does it end in .gem? install_from_gemfile(plugin_id_arg) elsif plugin_id_arg =~ %r{[\/\\]} || Dir.exist?(plugin_id_arg) # Does the argument have a slash, or exist as dir in the local directory? install_from_path(plugin_id_arg) else install_from_remote_gem(plugin_id_arg) end end
list()
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 31 def list plugin_statuses = Inspec::Plugin::V2::Registry.instance.plugin_statuses options[:all] = false if options[:core] || options[:user] || options[:system] plugin_statuses.select! do |status| type = status.installation_type options[:all] || (options[:core] && %i{core bundle}.include?(type)) || (options[:user] && %i{user_gem path}.include?(type)) || (options[:system] && :system_gem == type) end unless plugin_statuses.empty? ui.table do |t| t.header = ["Plugin Name", "Version", "Via", "ApiVer"] plugin_statuses.sort_by { |s| s.name.to_s }.each do |status| t << [ status.name, make_pretty_version(status), make_pretty_install_type(status), status.api_generation, ] end end end ui.plain_line(" #{plugin_statuses.count} plugin(s) total") puts end
search(search_term)
click to toggle source
Justification for disabling ABC: currently at 33.51/33
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 74 def search(search_term) # rubocop: disable Metrics/AbcSize search_results = installer.search(search_term, exact: options[:exact], source: options[:source]) # The search results have already been filtered by the reject list. But the # RejectList doesn't filter {inspec, train}-test-fixture because we need those # for testing. We want to hide those from users, so unless we know we're in # test mode, remove them. unless options[:'include-test-fixture'] search_results.delete("inspec-test-fixture") search_results.delete("train-test-fixture") end puts ui.bold(format(" %-30s%-50s\n", "Plugin Name", "Versions Available")) ui.line search_results.keys.sort.each do |plugin_name| versions = options[:all] ? search_results[plugin_name] : [search_results[plugin_name].first] versions = "(" + versions.join(", ") + ")" ui.plain_line(format(" %-30s%-50s", plugin_name, versions)) end ui.line ui.plain_line(" #{search_results.count} plugin(s) found") puts ui.exit Inspec::UI::EXIT_PLUGIN_ERROR if search_results.empty? rescue Inspec::Plugin::V2::SearchError => ex Inspec::Log.error ex.message ui.exit Inspec::UI::EXIT_USAGE_ERROR end
uninstall(plugin_name)
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 168 def uninstall(plugin_name) status = Inspec::Plugin::V2::Registry.instance[plugin_name.to_sym] unless status ui.plain_line("#{ui.red("No such plugin installed:", print: false)} #{plugin_name} is not " \ "installed - uninstall failed") ui.exit Inspec::UI::EXIT_USAGE_ERROR end installer = Inspec::Plugin::V2::Installer.instance pre_uninstall_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s } old_version = pre_uninstall_versions.join(", ") installer.uninstall(plugin_name) if status.installation_type == :path ui.bold(plugin_name + " path-based plugin install has been " \ "uninstalled\n") else ui.bold(plugin_name + " plugin, version #{old_version}, has " \ "been uninstalled\n") end ui.exit Inspec::UI::EXIT_NORMAL end
update(plugin_name)
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 136 def update(plugin_name) pre_update_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s } old_version = pre_update_versions.join(", ") update_preflight_check(plugin_name, pre_update_versions) begin installer.update(plugin_name) rescue Inspec::Plugin::V2::UpdateError => ex ui.plain_line("#{ui.red("Update error:", print: false)} #{ex.message} - update failed") ui.exit Inspec::UI::EXIT_USAGE_ERROR end post_update_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s } new_version = (post_update_versions - pre_update_versions).first ui.bold(plugin_name + " plugin, version #{old_version} -> " \ "#{new_version}, updated from rubygems.org\n") end
Private Instance Methods
check_plugin_name(plugin_name, action)
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 484 def check_plugin_name(plugin_name, action) unless plugin_name =~ /^(inspec|train)-/ ui.red("Invalid plugin name - #{plugin_name} - All inspec " \ "plugins must begin with either 'inspec-' or 'train-' " \ "- #{action} failed.\n") ui.exit Inspec::UI::EXIT_USAGE_ERROR end end
install_attempt_install(plugin_name)
click to toggle source
Rationale for RuboCop variance: This is a one-line method with heavy UX-focused error handling.
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 412 def install_attempt_install(plugin_name) # rubocop: disable Metrics/AbcSize installer.install(plugin_name, version: options[:version], source: options[:source]) rescue Inspec::Plugin::V2::PluginExcludedError => ex ui.red("Plugin on Exclusion List - #{plugin_name} is listed as an " \ "incompatible gem - refusing to install.\n") ui.plain_line("Rationale: #{ex.details.rationale}") ui.plain_line("Exclusion list location: " + File.join(Inspec.src_root, "etc", "plugin_filters.json")) ui.plain_line("If you disagree with this determination, please accept " \ "our apologies for the misunderstanding, and open an issue " \ "at https://github.com/inspec/inspec/issues/new") ui.exit Inspec::UI::EXIT_PLUGIN_ERROR rescue Inspec::Plugin::V2::InstallError raise if Inspec::Log.level == :debug results = installer.search(plugin_name, exact: true) source_host = URI(options[:source] || "https://rubygems.org/").host if results.empty? ui.red("No such plugin gem #{plugin_name} could be found on " \ "#{source_host} - installation failed.\n") elsif options[:version] && !results[plugin_name].include?(options[:version]) ui.red("No such version - #{plugin_name} exists, but no such " \ "version #{options[:version]} found on #{source_host} - " \ "installation failed.\n") else ui.red("Unknown error occurred - installation failed.\n") end ui.exit Inspec::UI::EXIT_USAGE_ERROR end
install_from_gemfile(gem_file)
click to toggle source
#¶ ↑
install breakdown
#¶ ↑
These are broken down because rubocop complained.
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 200 def install_from_gemfile(gem_file) unless File.exist? gem_file ui.red("No such plugin gem file #{gem_file} - installation failed.\n") ui.exit Inspec::UI::EXIT_USAGE_ERROR end plugin_name_parts = File.basename(gem_file, ".gem").split("-") version = plugin_name_parts.pop plugin_name = plugin_name_parts.join("-") check_plugin_name(plugin_name, "installation") installer.install(plugin_name, gem_file: gem_file) ui.bold("#{plugin_name} plugin, version #{version}, installed from " \ "local .gem file\n") ui.exit Inspec::UI::EXIT_NORMAL end
install_from_path(path)
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 218 def install_from_path(path) unless File.exist? path ui.red("No such source code path #{path} - installation failed.\n") ui.exit Inspec::UI::EXIT_USAGE_ERROR end plugin_name = File.basename(path, ".rb") # While installer.install does some rudimentary checking, # this file has good UI access, so we promise to validate the # input a lot and hand the installer a sure-thing. # Name OK? check_plugin_name(plugin_name, "installation") # Already installed? if registry.known_plugin?(plugin_name.to_sym) ui.bold("Plugin already installed - #{plugin_name} - Use '#{EXEC_NAME} " \ "plugin list' to see previously installed plugin - " \ "installation failed.\n") ui.exit Inspec::UI::EXIT_NORMAL end # Can we figure out how to load it? entry_point = install_from_path__apply_entry_point_heuristics(path) # If you load it, does it act like a plugin? install_from_path__probe_load(entry_point, plugin_name) # OK, install it! installer.install(plugin_name, path: entry_point) ui.bold("#{plugin_name} plugin installed via source path reference, " \ "resolved to entry point #{entry_point}\n") ui.exit Inspec::UI::EXIT_NORMAL end
install_from_path__apply_entry_point_heuristics(path)
click to toggle source
Rationale for rubocop variances: It's a heuristics method, and will be full of conditionals. The code is well-commented; refactoring into sub-methods would reduce clarity.
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 258 def install_from_path__apply_entry_point_heuristics(path) # rubocop: disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity given = Pathname.new(path) given = given.expand_path # Resolve any relative paths name_regex = /^(inspec|train)-/ versioned_regex = /^(inspec|train)-[a-z0-9\-\_]+-\d+\.\d+\.\d+$/ sha_ref_regex = /^(inspec|train)-[a-z0-9\-\_]+-[0-9a-f]{5,40}$/ # What are the last four things like? parts = [ given.parent.parent.basename, given.parent.basename, given.basename(".rb"), given.extname, ].map(&:to_s) # Case 1: Simplest case: it was a full entry point, as presented. # /home/you/projects/inspec-something/lib/inspec-something.rb # parts index: ^0^ ^1^ ^2^ ^3^ if parts[0] =~ name_regex && parts[1] == "lib" && parts[2] == parts[0] && parts[3] == ".rb" return given.to_s end # Case 2: Also easy: they either referred to the internal library directory, # or left the extansion off. Those are the same to us. # /home/you/projects/inspec-something/lib/inspec-something # parts index: ^0^ ^1^ ^2^ (3 is empty) if parts[0] =~ name_regex && parts[1] == "lib" && parts[2] == parts[0] && parts[3].empty? return given.to_s + ".rb" end # Case 3: Maybe they were refering to a path that is inside a gem installation, or an exploded gem? # In that case, we'll have a version on the plugin name in part 0 # /home/you/.gems/2.4.0/gems/inspec-something-3.45.1/lib/inspec-something.rb # parts index: ^0^ ^1^ ^2^ ^3^ if (parts[0] =~ versioned_regex || parts[0] =~ sha_ref_regex) && parts[1] == "lib" && parts[0].start_with?(parts[2]) && parts[3] == ".rb" return given.to_s end # Case 4: Like case 3, but missing the .rb # /home/you/.gems/2.4.0/gems/inspec-something-3.45.1/lib/inspec-something # parts index: ^0^ ^1^ ^2^ ^3^ (empty) if (parts[0] =~ versioned_regex || parts[0] =~ sha_ref_regex) && parts[1] == "lib" && parts[0].start_with?(parts[2]) && parts[3].empty? return given.to_s + ".rb" end # Case 5: Easy to recognize, but harder to handle: they referred to the project root. # /home/you/projects/inspec-something # parts index: ^0^ ^1^ ^2^ (3 is empty) # 0 and 1 are not meaningful to us, but we hope to find a parts[2]/lib/inspec-something.rb. entry_point_guess = File.join(given.to_s, "lib", parts[2] + ".rb") if parts[2] =~ name_regex && File.exist?(entry_point_guess) return entry_point_guess end # Well, if we got here, parts[2] matches an inspec/train prefix, but we have no idea about anything. # Give up. ui.red("Unrecognizable plugin structure - #{parts[2]} - When " \ "installing from a path, please provide the path of the " \ "entry point file - installation failed.\n") ui.exit Inspec::UI::EXIT_USAGE_ERROR end
install_from_path__probe_load(entry_point, plugin_name)
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 320 def install_from_path__probe_load(entry_point, plugin_name) # Brazenly attempt to load a file, and see if it registers a plugin. begin require entry_point rescue LoadError => ex ui.red("Plugin contains errors - #{plugin_name} - Encountered " \ "errors while trying to test load the plugin entry point, " \ "resolved to #{entry_point} - installation failed\n") ui.plain_line ex.message ui.exit Inspec::UI::EXIT_USAGE_ERROR end # OK, the wheels didn't fall off. But is it a plugin? if plugin_name.to_s.start_with?("train") # Train internal names do not include the prix in their registry entries # And the registry is keyed on Strings registry_key = plugin_name.to_s.sub(/^train-/, "") unless Train::Plugins.registry.key?(registry_key) ui.red("Does not appear to be a plugin - #{plugin_name} - After " \ "probe-loading the supposed plugin, it did not register " \ "itself to Train. Ensure something inherits from " \ "'Train.plugin(1)' - installation failed.\n") ui.exit Inspec::UI::EXIT_USAGE_ERROR end else unless registry.known_plugin?(plugin_name.to_sym) ui.red("Does not appear to be a plugin - #{plugin_name} - After " \ "probe-loading the supposed plugin, it did not register " \ "itself to InSpec. Ensure something inherits from " \ "'Inspec.plugin(2)' - installation failed.\n") ui.exit Inspec::UI::EXIT_USAGE_ERROR end end end
install_from_remote_gem(plugin_name)
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 355 def install_from_remote_gem(plugin_name) requested_version = options[:version] check_plugin_name(plugin_name, "installation") # Version pre-flighting pre_installed_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s } install_from_remote_gem_verson_preflight_check(plugin_name, requested_version, pre_installed_versions) install_attempt_install(plugin_name) # Success messaging. What did we actually install? post_installed_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s } new_version = (post_installed_versions - pre_installed_versions).first ui.bold("#{plugin_name} plugin, version #{new_version}, installed " \ "from rubygems.org\n") ui.exit Inspec::UI::EXIT_NORMAL end
install_from_remote_gem_verson_preflight_check(plugin_name, requested_version, pre_installed_versions)
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 375 def install_from_remote_gem_verson_preflight_check(plugin_name, requested_version, pre_installed_versions) return if pre_installed_versions.empty? # Everything past here in the block is a code 2 error # If they didn't ask for a specific version, they implicitly ask for the latest. # Do an expensive search to determine the latest version. unless requested_version latest_version = installer.search(plugin_name, exact: true, scope: :latest) latest_version = latest_version[plugin_name]&.last if latest_version && !requested_version requested_version = latest_version end end # Check for already-installed at desired version conditions they_explicitly_asked_for_a_version = !options[:version].nil? what_we_would_install_is_already_installed = pre_installed_versions.include?(requested_version) if what_we_would_install_is_already_installed && they_explicitly_asked_for_a_version ui.bold("Plugin already installed at requested version - plugin " \ "#{plugin_name} #{requested_version} - refusing to install.\n") ui.exit Inspec::UI::EXIT_NORMAL elsif what_we_would_install_is_already_installed && !they_explicitly_asked_for_a_version ui.red("Plugin already installed at latest version - plugin " \ "#{plugin_name} #{requested_version} - refusing to install.\n") ui.exit Inspec::UI::EXIT_NORMAL end # There are existing versions installed, but none of them are what was requested ui.red("Update required - plugin #{plugin_name}, requested " \ "#{requested_version}, have " \ "#{pre_installed_versions.join(", ")}; use `inspec " \ "plugin update` - refusing to install.\n") ui.exit Inspec::UI::EXIT_PLUGIN_ERROR end
installer()
click to toggle source
make_pretty_install_type(status)
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 517 def make_pretty_install_type(status) INSTALL_TYPE_LABELS[status.installation_type] end
make_pretty_version(status)
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 493 def make_pretty_version(status) case status.installation_type when :core, :bundle Inspec::VERSION when :user_gem, :system_gem if status.version.nil? "(unknown)" elsif status.version =~ /^\d+\.\d+\.\d+$/ status.version else # Assume it is a version constraint string and try to resolve # TODO: this is naive, and assumes the latest version is the one that will be used. Logged on #3317 # In fact, the logic to determine "what version would be used" belongs in the Loader. plugin_name = status.name.to_s Inspec::Plugin::V2::Loader.list_installed_plugin_gems .select { |spec| spec.name == plugin_name } .max_by(&:version) .version end when :path "src" end end
registry()
click to toggle source
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 480 def registry Inspec::Plugin::V2::Registry.instance end
update_preflight_check(plugin_name, pre_update_versions)
click to toggle source
#¶ ↑
update breakdown
#¶ ↑
# File lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb, line 445 def update_preflight_check(plugin_name, pre_update_versions) if pre_update_versions.empty? # Check for path install status = Inspec::Plugin::V2::Registry.instance[plugin_name.to_sym] if !status ui.plain_line("#{ui.red("No such plugin installed:", print: false)} #{plugin_name} - update failed") ui.exit Inspec::UI::EXIT_USAGE_ERROR elsif status.installation_type == :path ui.plain_line("#{ui.red("Cannot update path-based install:", print: false)} " \ "#{plugin_name} is installed via path reference; " \ "use `inspec plugin uninstall` to remove - refusing to" \ "update") ui.exit Inspec::UI::EXIT_PLUGIN_ERROR end end # Check for latest version (and implicitly, existence) latest_version = installer.search(plugin_name, exact: true, scope: :latest) latest_version = latest_version[plugin_name]&.last if pre_update_versions.include?(latest_version) ui.plain_line("#{ui.bold("Already installed at latest version:", print: false)} " \ "#{plugin_name} is at #{latest_version}, which the " \ "latest - refusing to update") ui.exit Inspec::UI::EXIT_NORMAL end end