inspec-core 2.2.112 → 2.3.4

Sign up to get free protection for your applications and to get access to all the features.
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