inspec-core 2.2.112 → 2.3.4

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -19
  3. data/README.md +1 -1
  4. data/docs/dev/integration-testing.md +31 -0
  5. data/docs/dev/plugins.md +4 -2
  6. data/docs/dsl_inspec.md +104 -4
  7. data/docs/plugins.md +57 -0
  8. data/docs/style.md +178 -0
  9. data/examples/plugins/inspec-resource-lister/Gemfile +12 -0
  10. data/examples/plugins/inspec-resource-lister/LICENSE +13 -0
  11. data/examples/plugins/inspec-resource-lister/README.md +62 -0
  12. data/examples/plugins/inspec-resource-lister/Rakefile +40 -0
  13. data/examples/plugins/inspec-resource-lister/inspec-resource-lister.gemspec +45 -0
  14. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister.rb +16 -0
  15. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister/cli_command.rb +70 -0
  16. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister/plugin.rb +55 -0
  17. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister/version.rb +10 -0
  18. data/examples/plugins/inspec-resource-lister/test/fixtures/README.md +24 -0
  19. data/examples/plugins/inspec-resource-lister/test/functional/README.md +18 -0
  20. data/examples/plugins/inspec-resource-lister/test/functional/inspec_resource_lister_test.rb +110 -0
  21. data/examples/plugins/inspec-resource-lister/test/helper.rb +26 -0
  22. data/examples/plugins/inspec-resource-lister/test/unit/README.md +17 -0
  23. data/examples/plugins/inspec-resource-lister/test/unit/cli_args_test.rb +64 -0
  24. data/examples/plugins/inspec-resource-lister/test/unit/plugin_def_test.rb +51 -0
  25. data/examples/profile/controls/example.rb +9 -8
  26. data/inspec-core.gemspec +1 -1
  27. data/lib/inspec/attribute_registry.rb +1 -1
  28. data/lib/inspec/globals.rb +4 -0
  29. data/lib/inspec/objects/control.rb +18 -3
  30. data/lib/inspec/plugin/v2.rb +14 -3
  31. data/lib/inspec/plugin/v2/activator.rb +7 -2
  32. data/lib/inspec/plugin/v2/installer.rb +426 -0
  33. data/lib/inspec/plugin/v2/loader.rb +137 -30
  34. data/lib/inspec/plugin/v2/registry.rb +13 -4
  35. data/lib/inspec/profile.rb +2 -1
  36. data/lib/inspec/reporters/json.rb +11 -1
  37. data/lib/inspec/resource.rb +6 -15
  38. data/lib/inspec/rule.rb +18 -9
  39. data/lib/inspec/runner_rspec.rb +1 -1
  40. data/lib/inspec/schema.rb +1 -0
  41. data/lib/inspec/version.rb +1 -1
  42. data/lib/plugins/inspec-plugin-manager-cli/README.md +6 -0
  43. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli.rb +18 -0
  44. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +420 -0
  45. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/plugin.rb +12 -0
  46. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/config_dirs/empty/.gitkeep +0 -0
  47. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/inspec-egg-white-omelette/lib/inspec-egg-white-omelette.rb +2 -0
  48. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/inspec-egg-white-omelette/lib/inspec-egg-white-omelette/.gitkeep +0 -0
  49. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/inspec-wrong-structure/.gitkeep +0 -0
  50. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/wrong-name/lib/wrong-name.rb +1 -0
  51. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/wrong-name/lib/wrong-name/.gitkeep +0 -0
  52. data/lib/plugins/inspec-plugin-manager-cli/test/functional/inspec-plugin_test.rb +651 -0
  53. data/lib/plugins/inspec-plugin-manager-cli/test/unit/cli_args_test.rb +71 -0
  54. data/lib/plugins/inspec-plugin-manager-cli/test/unit/plugin_def_test.rb +20 -0
  55. data/lib/plugins/shared/core_plugin_test_helper.rb +101 -2
  56. data/lib/plugins/things-for-train-integration.rb +14 -0
  57. data/lib/resources/port.rb +10 -6
  58. metadata +38 -11
  59. data/docs/ruby_usage.md +0 -204
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '2.2.112'
7
+ VERSION = '2.3.4'
8
8
  end
@@ -0,0 +1,6 @@
1
+ # InSpec Plugin Manager CLI
2
+
3
+ This is a CLI plugin for InSpec. It uses the Plugins API v2 to create a
4
+ series of commands to manage plugins.
5
+
6
+ It was the first plugin to be authored as a core plugin under Plugins v2.
@@ -0,0 +1,18 @@
1
+
2
+ # Because this is a core plugin, we place the plugin definition here in the entry point.
3
+ # This is needed because under core testing, the entry point may be reloaded multiple times,
4
+ # and we need plugin registration to properly occur each time.
5
+ # More typically, the entry point would just load a plugin definition file.
6
+
7
+ module InspecPlugins
8
+ module PluginManager
9
+ class Plugin < Inspec.plugin(2)
10
+ plugin_name :'inspec-plugin-manager-cli'
11
+
12
+ cli_command :plugin do
13
+ require_relative 'inspec-plugin-manager-cli/cli_command'
14
+ InspecPlugins::PluginManager::CliCommand
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,420 @@
1
+ require 'term/ansicolor'
2
+ require 'pathname'
3
+ require 'inspec/plugin/v2/installer'
4
+
5
+ module InspecPlugins
6
+ module PluginManager
7
+ class CliCommand < Inspec.plugin(2, :cli_command)
8
+ include Term::ANSIColor
9
+
10
+ subcommand_desc 'plugin SUBCOMMAND', 'Manage InSpec and Train plugins'
11
+
12
+ #==================================================================#
13
+ # inspec plugin list
14
+ #==================================================================#
15
+
16
+ desc 'list [options]', 'Lists user-installed InSpec plugins.'
17
+ option :all, desc: 'Include plugins shipped with InSpec as well.', type: :boolean, aliases: [:a]
18
+ def list
19
+ plugin_statuses = Inspec::Plugin::V2::Registry.instance.plugin_statuses
20
+ plugin_statuses.reject! { |s| [:core, :bundle].include?(s.installation_type) } unless options[:all]
21
+
22
+ # TODO: ui object support
23
+ puts
24
+ puts(bold { format(' %-30s%-10s%-8s%-6s', 'Plugin Name', 'Version', 'Via', 'ApiVer') })
25
+ puts '-' * 55
26
+ plugin_statuses.sort_by(&:name).each do |status|
27
+ puts(format(' %-30s%-10s%-8s%-6s', status.name, make_pretty_version(status), status.installation_type, status.api_generation.to_s))
28
+ end
29
+ puts '-' * 55
30
+ puts(" #{plugin_statuses.count} plugin(s) total")
31
+ puts
32
+ end
33
+
34
+ #==================================================================#
35
+ # inspec plugin search
36
+ #==================================================================#
37
+
38
+ desc 'search [options] PATTERN', 'Searches rubygems.org for InSpec plugins. Exits 0 on a search hit, exits 2 on a search miss.'
39
+ option :all, desc: 'List all available versions, not just the latest one.', type: :boolean, aliases: [:a]
40
+ option :exact, desc: 'Assume PATTERN is exact; do not add a wildcard to the end', type: :boolean, aliases: [:e]
41
+ # Justification for disabling ABC: currently at 33.51/33
42
+ def search(search_term) # rubocop: disable Metrics/AbcSize
43
+ search_results = installer.search(search_term, exact: options[:exact])
44
+
45
+ # TODO: ui object support
46
+ puts
47
+ puts(bold { format(' %-30s%-50s%', 'Plugin Name', 'Versions Available') })
48
+ puts '-' * 55
49
+ search_results.keys.sort.each do |plugin_name|
50
+ versions = options[:all] ? search_results[plugin_name] : [search_results[plugin_name].first]
51
+ versions = '(' + versions.join(', ') + ')'
52
+ puts(format(' %-30s%-50s', plugin_name, versions))
53
+ end
54
+ puts '-' * 55
55
+ puts(" #{search_results.count} plugin(s) found")
56
+ puts
57
+
58
+ exit 2 if search_results.empty?
59
+ rescue Inspec::Plugin::V2::SearchError => ex
60
+ Inspec::Log.error ex.message
61
+ exit 1
62
+ end
63
+
64
+ #==================================================================#
65
+ # inspec plugin install
66
+ #==================================================================#
67
+ desc 'install [-v VERSION] PLUGIN', 'Installs a plugin from rubygems.org, a gemfile, or a path to local source.'
68
+ long_desc <<~EOLD
69
+ PLUGIN may be the name of a gem on rubygems.org that begins with inspec- or train-.
70
+ PLUGIN may also be the path to a local gemfile, which will then be installed like
71
+ any other gem. Finally, if PLUGIN is a path ending in .rb, it is taken to be a
72
+ local file that will act as athe entry point for a plugin (this mode is provided
73
+ for local plugin development). Exit codes are 0 on success, 2 if the plugin is
74
+ already installed, and 1 if any other error occurs.
75
+ EOLD
76
+ option :version, desc: 'When installing from rubygems.org, specifies a specific version to install.', aliases: [:v]
77
+ def install(plugin_id_arg)
78
+ if plugin_id_arg =~ /\.gem$/ # Does it end in .gem?
79
+ install_from_gemfile(plugin_id_arg)
80
+ elsif plugin_id_arg =~ %r{[\/\\]} || Dir.exist?(plugin_id_arg) # Does the argument have a slash, or exist as dir in the local directory?
81
+ install_from_path(plugin_id_arg)
82
+ else
83
+ install_from_remote_gem(plugin_id_arg)
84
+ end
85
+ end
86
+
87
+ #--------------------------
88
+ # update
89
+ #--------------------------
90
+ desc 'update PLUGIN', 'Updates a plugin to the latest from from rubygems.org'
91
+ long_desc <<~EOLD
92
+ PLUGIN may be the name of a gem on rubygems.org that begins with inspec- or train-.
93
+ Exit codes are 0 on success, 2 if the plugin is already up to date, and 1 if any
94
+ other error occurs.
95
+ EOLD
96
+ def update(plugin_name)
97
+ pre_update_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
98
+ old_version = pre_update_versions.join(', ')
99
+
100
+ update_preflight_check(plugin_name, pre_update_versions)
101
+
102
+ begin
103
+ installer.update(plugin_name)
104
+ rescue Inspec::Plugin::V2::UpdateError => ex
105
+ puts(red { 'Update error: ' } + ex.message + ' - update failed')
106
+ exit 1
107
+ end
108
+ post_update_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
109
+ new_version = (post_update_versions - pre_update_versions).first
110
+
111
+ puts(bold { plugin_name } + " plugin, version #{old_version} -> #{new_version}, updated from rubygems.org")
112
+ end
113
+
114
+ #--------------------------
115
+ # uninstall
116
+ #--------------------------
117
+ desc 'uninstall PLUGIN_NAME', 'Uninstalls a gem- or path- based plugin'
118
+ long_desc <<~EOLD
119
+ Removes a plugin from the users configuration.
120
+ In the case of a gem plugin (by far the most common), the plugin gem is removed, along
121
+ with any of its dependencies that are no longer needed by anything else. Finally, the
122
+ plugin configuration file is updated to reflect that the plugin is no longer present.
123
+ In the case of a path-based plugin (often used for plugin development), no changes
124
+ are made to the referenced plugin source code. Rather, the plugin's entry is simply removed
125
+ from the plugin config file.
126
+ EOLD
127
+ def uninstall(plugin_name)
128
+ status = Inspec::Plugin::V2::Registry.instance[plugin_name.to_sym]
129
+ unless status
130
+ puts(red { 'No such plugin installed: ' } + "#{plugin_name} is not installed - uninstall failed")
131
+
132
+ exit 1
133
+ end
134
+ installer = Inspec::Plugin::V2::Installer.instance
135
+
136
+ pre_uninstall_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
137
+ old_version = pre_uninstall_versions.join(', ')
138
+
139
+ installer.uninstall(plugin_name)
140
+
141
+ if status.installation_type == :path
142
+ puts(bold { plugin_name } + ' path-based plugin install has been uninstalled')
143
+ else
144
+ puts(bold { plugin_name } + " plugin, version #{old_version}, has been uninstalled")
145
+ end
146
+ exit 0
147
+ end
148
+
149
+ private
150
+
151
+ #==================================================================#
152
+ # install breakdown
153
+ #==================================================================#
154
+ # These are broken down because rubocop complained.
155
+
156
+ def install_from_gemfile(gem_file)
157
+ unless File.exist? gem_file
158
+ puts(red { 'No such plugin gem file ' } + gem_file + ' - installation failed.')
159
+ exit 1
160
+ end
161
+
162
+ plugin_name_parts = File.basename(gem_file, '.gem').split('-')
163
+ version = plugin_name_parts.pop
164
+ plugin_name = plugin_name_parts.join('-')
165
+ check_plugin_name(plugin_name, 'installation')
166
+
167
+ installer.install(plugin_name, gem_file: gem_file)
168
+
169
+ puts(bold { plugin_name } + " plugin, version #{version}, installed from local .gem file")
170
+ exit 0
171
+ end
172
+
173
+ def install_from_path(path)
174
+ unless File.exist? path
175
+ puts(red { 'No such source code path ' } + path + ' - installation failed.')
176
+ exit 1
177
+ end
178
+
179
+ plugin_name = File.basename(path, '.rb')
180
+
181
+ # While installer.install does some rudimentary checking,
182
+ # this file has good UI access, so we promise to validate the
183
+ # input a lot and hand the installer a sure-thing.
184
+
185
+ # Name OK?
186
+ check_plugin_name(plugin_name, 'installation')
187
+
188
+ # Already installed?
189
+ if registry.known_plugin?(plugin_name.to_sym)
190
+ puts(red { 'Plugin already installed' } + " - #{plugin_name} - Use 'inspec plugin list' to see previously installed plugin - installation failed.")
191
+ exit 2
192
+ end
193
+
194
+ # Can we figure out how to load it?
195
+ entry_point = install_from_path__apply_entry_point_heuristics(path)
196
+
197
+ # If you load it, does it act like a plugin?
198
+ install_from_path__probe_load(entry_point, plugin_name)
199
+
200
+ # OK, install it!
201
+ installer.install(plugin_name, path: entry_point)
202
+
203
+ puts(bold { plugin_name } + ' plugin installed via source path reference, resolved to entry point ' + entry_point)
204
+ exit 0
205
+ end
206
+
207
+ # Rationale for rubocop variances: It's a heuristics method, and will be full of
208
+ # conditionals. The code is well-commented; refactoring into sub-methods would
209
+ # reduce clarity.
210
+ def install_from_path__apply_entry_point_heuristics(path) # rubocop: disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
211
+ given = Pathname.new(path)
212
+ given = given.expand_path # Resolve any relative paths
213
+ name_regex = /^(inspec|train)-/
214
+ versioned_regex = /^(inspec|train)-[a-z0-9\-\_]+-\d+\.\d+\.\d+$/
215
+
216
+ # What are the last four things like?
217
+ parts = [
218
+ given.parent.parent.basename,
219
+ given.parent.basename,
220
+ given.basename('.rb'),
221
+ given.extname,
222
+ ].map(&:to_s)
223
+
224
+ # Case 1: Simplest case: it was a full entry point, as presented.
225
+ # /home/you/projects/inspec-something/lib/inspec-something.rb
226
+ # parts index: ^0^ ^1^ ^2^ ^3^
227
+ if parts[0] =~ name_regex && parts[1] == 'lib' && parts[2] == parts[0] && parts[3] == '.rb'
228
+ return given.to_s
229
+ end
230
+
231
+ # Case 2: Also easy: they either referred to the internal library directory,
232
+ # or left the extansion off. Those are the same to us.
233
+ # /home/you/projects/inspec-something/lib/inspec-something
234
+ # parts index: ^0^ ^1^ ^2^ (3 is empty)
235
+ if parts[0] =~ name_regex && parts[1] == 'lib' && parts[2] == parts[0] && parts[3].empty?
236
+ return given.to_s + '.rb'
237
+ end
238
+
239
+ # Case 3: Maybe they were refering to a path that is inside a gem installation, or an exploded gem?
240
+ # In that case, we'll have a version on the plugin name in part 0
241
+ # /home/you/.gems/2.4.0/gems/inspec-something-3.45.1/lib/inspec-something.rb
242
+ # parts index: ^0^ ^1^ ^2^ ^3^
243
+ if parts[0] =~ versioned_regex && parts[1] == 'lib' && parts[0].start_with?(parts[2]) && parts[3] == '.rb'
244
+ return given.to_s
245
+ end
246
+
247
+ # Case 4: Like case 3, but missing the .rb
248
+ # /home/you/.gems/2.4.0/gems/inspec-something-3.45.1/lib/inspec-something
249
+ # parts index: ^0^ ^1^ ^2^ ^3^ (empty)
250
+ if parts[0] =~ versioned_regex && parts[1] == 'lib' && parts[0].start_with?(parts[2]) && parts[3].empty?
251
+ return given.to_s + '.rb'
252
+ end
253
+
254
+ # Case 5: Easy to recognize, but harder to handle: they referred to the project root.
255
+ # /home/you/projects/inspec-something
256
+ # parts index: ^0^ ^1^ ^2^ (3 is empty)
257
+ # 0 and 1 are not meaningful to us, but we hope to find a parts[2]/lib/inspec-something.rb.
258
+ entry_point_guess = File.join(given.to_s, 'lib', parts[2] + '.rb')
259
+ if parts[2] =~ name_regex && File.exist?(entry_point_guess)
260
+ return entry_point_guess
261
+ end
262
+
263
+ # Well, if we got here, parts[2] matches an inspec/train prefix, but we have no idea about anything.
264
+ # Give up.
265
+ puts(red { 'Unrecognizable plugin structure' } + " - #{parts[2]} - When installing from a path, please provide the path of the entry point file - installation failed.")
266
+ exit 1
267
+ end
268
+
269
+ def install_from_path__probe_load(entry_point, plugin_name)
270
+ # Brazenly attempt to load a file, and see if it registers a plugin.
271
+ begin
272
+ require entry_point
273
+ rescue LoadError => ex
274
+ puts(red { 'Plugin contains errors' } + " - #{plugin_name} - Encountered errors while trying to test load the plugin entry point, resolved to #{entry_point} - installation failed")
275
+ puts ex.message
276
+ exit 1
277
+ end
278
+
279
+ # OK, the wheels didn't fall off. But is it a plugin?
280
+ if plugin_name.to_s.start_with?('train')
281
+ # Train internal names do not include the prix in their registry entries
282
+ # And the registry is keyed on Strings
283
+ registry_key = plugin_name.to_s.sub(/^train-/, '')
284
+ unless Train::Plugins.registry.key?(registry_key)
285
+ puts(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.")
286
+ exit 1
287
+ end
288
+ else
289
+ unless registry.known_plugin?(plugin_name.to_sym)
290
+ puts(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.")
291
+ exit 1
292
+ end
293
+ end
294
+ end
295
+
296
+ def install_from_remote_gem(plugin_name)
297
+ requested_version = options[:version]
298
+
299
+ check_plugin_name(plugin_name, 'installation')
300
+
301
+ # Version pre-flighting
302
+ pre_installed_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
303
+ install_from_remote_gem_verson_preflight_check(plugin_name, requested_version, pre_installed_versions)
304
+
305
+ install_attempt_install(plugin_name)
306
+
307
+ # Success messaging. What did we actually install?
308
+ post_installed_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
309
+ new_version = (post_installed_versions - pre_installed_versions).first
310
+
311
+ puts(bold { plugin_name } + " plugin, version #{new_version}, installed from rubygems.org")
312
+ exit 0
313
+ end
314
+
315
+ def install_from_remote_gem_verson_preflight_check(plugin_name, requested_version, pre_installed_versions)
316
+ return if pre_installed_versions.empty?
317
+
318
+ # Everything past here in the block is a code 2 error
319
+
320
+ # If they didn't ask for a specific version, they implicitly ask for the latest.
321
+ # Do an expensive search to determine the latest version.
322
+ unless requested_version
323
+ latest_version = installer.search(plugin_name, exact: true, scope: :latest)
324
+ latest_version = latest_version[plugin_name]&.last
325
+ if latest_version && !requested_version
326
+ requested_version = latest_version
327
+ end
328
+ end
329
+
330
+ # Check for already-installed at desired version conditions
331
+ they_explicitly_asked_for_a_version = !options[:version].nil?
332
+ what_we_would_install_is_already_installed = pre_installed_versions.include?(requested_version)
333
+ if what_we_would_install_is_already_installed && they_explicitly_asked_for_a_version
334
+ puts(red { 'Plugin already installed at requested version' } + " - plugin #{plugin_name} #{requested_version} - refusing to install.")
335
+ elsif what_we_would_install_is_already_installed && !they_explicitly_asked_for_a_version
336
+ puts(red { 'Plugin already installed at latest version' } + " - plugin #{plugin_name} #{requested_version} - refusing to install.")
337
+ else
338
+ # There are existing versions installed, but none of them are what was requested
339
+ puts(red { 'Update required' } + " - plugin #{plugin_name}, requested #{requested_version}, have #{pre_installed_versions.join(', ')}; use `inspec plugin update` - refusing to install.")
340
+ end
341
+
342
+ exit 2
343
+ end
344
+
345
+ def install_attempt_install(plugin_name)
346
+ installer.install(plugin_name, version: options[:version])
347
+ rescue Inspec::Plugin::V2::InstallError
348
+ results = installer.search(plugin_name, exact: true)
349
+ if results.empty?
350
+ puts(red { 'No such plugin gem ' } + plugin_name + ' could be found on rubygems.org - installation failed.')
351
+ elsif options[:version] && !results[plugin_name].include?(options[:version])
352
+ puts(red { 'No such version' } + ' - ' + plugin_name + " exists, but no such version #{options[:version]} found on rubygems.org - installation failed.")
353
+ else
354
+ puts(red { 'Unknown error occured ' } + ' - installation failed.')
355
+ end
356
+ exit 1
357
+ end
358
+
359
+ #==================================================================#
360
+ # update breakdown
361
+ #==================================================================#
362
+ def update_preflight_check(plugin_name, pre_update_versions)
363
+ if pre_update_versions.empty?
364
+ # Check for path install
365
+ status = Inspec::Plugin::V2::Registry.instance[plugin_name.to_sym]
366
+ if !status
367
+ puts(red { 'No such plugin installed: ' } + "#{plugin_name} - update failed")
368
+ exit 1
369
+ elsif status.installation_type == :path
370
+ puts(red { 'Cannot update path-based install: ' } + "#{plugin_name} is installed via path reference; use `inspec plugin uninstall` to remove - refusing to update")
371
+ exit 2
372
+ end
373
+ end
374
+
375
+ # Check for latest version (and implicitly, existance)
376
+ latest_version = installer.search(plugin_name, exact: true, scope: :latest)
377
+ latest_version = latest_version[plugin_name]&.last
378
+
379
+ if pre_update_versions.include?(latest_version)
380
+ puts(red { 'Already installed at latest version: ' } + "#{plugin_name} is at #{latest_version}, which the latest - refusing to update")
381
+ exit 2
382
+ end
383
+ end
384
+
385
+ #==================================================================#
386
+ # utilities
387
+ #==================================================================#
388
+ def installer
389
+ Inspec::Plugin::V2::Installer.instance
390
+ end
391
+
392
+ def registry
393
+ Inspec::Plugin::V2::Registry.instance
394
+ end
395
+
396
+ def check_plugin_name(plugin_name, action)
397
+ unless plugin_name =~ /^(inspec|train)-/
398
+ puts(red { 'Invalid plugin name' } + " - #{plugin_name} - All inspec plugins must begin with either 'inspec-' or 'train-' - #{action} failed.")
399
+ exit 1
400
+ end
401
+ end
402
+
403
+ def make_pretty_version(status)
404
+ case status.installation_type
405
+ when :core, :bundle
406
+ Inspec::VERSION
407
+ when :gem
408
+ # TODO: this is naive, and assumes the latest version is the one that will be used. Logged on #3317
409
+ # In fact, the logic to determine "what version would be used" belongs in the Loader.
410
+ Inspec::Plugin::V2::Loader.list_installed_plugin_gems
411
+ .select { |spec| spec.name == status.name.to_s }
412
+ .sort_by(&:version)
413
+ .last.version
414
+ when :path
415
+ 'src'
416
+ end
417
+ end
418
+ end
419
+ end
420
+ end