inspec-core 6.8.24 → 7.0.38.beta

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -6
  3. data/etc/deprecations.json +15 -6
  4. data/lib/inspec/base_cli.rb +3 -0
  5. data/lib/inspec/cached_fetcher.rb +16 -1
  6. data/lib/inspec/dependencies/cache.rb +48 -4
  7. data/lib/inspec/dsl.rb +40 -11
  8. data/lib/inspec/exceptions.rb +1 -0
  9. data/lib/inspec/fetcher/gem.rb +99 -0
  10. data/lib/inspec/fetcher/local.rb +1 -1
  11. data/lib/inspec/fetcher.rb +1 -0
  12. data/lib/inspec/file_provider.rb +46 -1
  13. data/lib/inspec/input_registry.rb +1 -1
  14. data/lib/inspec/plugin/v2/concerns/gem_spec_helper.rb +30 -0
  15. data/lib/inspec/plugin/v2/gem_source_manager.rb +43 -0
  16. data/lib/inspec/plugin/v2/installer.rb +42 -16
  17. data/lib/inspec/plugin/v2/loader.rb +34 -5
  18. data/lib/inspec/plugin/v2/plugin_types/resource_pack.rb +8 -0
  19. data/lib/inspec/plugin/v2.rb +1 -0
  20. data/lib/inspec/profile.rb +10 -0
  21. data/lib/inspec/profile_context.rb +10 -0
  22. data/lib/inspec/reporters/automate.rb +2 -2
  23. data/lib/inspec/resources/auditd.rb +1 -1
  24. data/lib/inspec/resources/groups.rb +52 -0
  25. data/lib/inspec/resources/port.rb +2 -2
  26. data/lib/inspec/resources/postgres_session.rb +5 -9
  27. data/lib/inspec/resources/yum.rb +1 -1
  28. data/lib/inspec/resources.rb +0 -14
  29. data/lib/inspec/runner.rb +7 -15
  30. data/lib/inspec/source_reader.rb +2 -0
  31. data/lib/inspec/ui.rb +1 -0
  32. data/lib/inspec/utils/deprecation/config_file.rb +39 -3
  33. data/lib/inspec/utils/deprecation/deprecator.rb +10 -3
  34. data/lib/inspec/utils/licensing_config.rb +1 -15
  35. data/lib/inspec/utils/parser.rb +9 -19
  36. data/lib/inspec/utils/telemetry.rb +1 -3
  37. data/lib/inspec/version.rb +1 -1
  38. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +2 -4
  39. data/lib/source_readers/gem.rb +67 -0
  40. data/lib/source_readers/inspec.rb +1 -1
  41. metadata +9 -32
  42. data/lib/inspec/resources/docker.rb +0 -274
  43. data/lib/inspec/resources/docker_container.rb +0 -116
  44. data/lib/inspec/resources/docker_image.rb +0 -141
  45. data/lib/inspec/resources/docker_object.rb +0 -52
  46. data/lib/inspec/resources/docker_plugin.rb +0 -68
  47. data/lib/inspec/resources/docker_service.rb +0 -95
  48. data/lib/inspec/resources/elasticsearch.rb +0 -165
  49. data/lib/inspec/resources/ibmdb2_conf.rb +0 -65
  50. data/lib/inspec/resources/ibmdb2_session.rb +0 -78
  51. data/lib/inspec/resources/mongodb.rb +0 -69
  52. data/lib/inspec/resources/mongodb_conf.rb +0 -44
  53. data/lib/inspec/resources/mongodb_session.rb +0 -98
  54. data/lib/inspec/resources/podman.rb +0 -353
  55. data/lib/inspec/resources/podman_container.rb +0 -84
  56. data/lib/inspec/resources/podman_image.rb +0 -108
  57. data/lib/inspec/resources/podman_network.rb +0 -81
  58. data/lib/inspec/resources/podman_pod.rb +0 -101
  59. data/lib/inspec/resources/podman_volume.rb +0 -87
  60. data/lib/inspec/resources/rabbitmq_conf.rb +0 -2
  61. data/lib/inspec/resources/rabbitmq_config.rb +0 -56
  62. data/lib/inspec/resources/ssh_config.rb +0 -215
  63. data/lib/inspec/resources/ssh_key.rb +0 -124
  64. data/lib/inspec/resources/sshd_active_config.rb +0 -2
  65. data/lib/inspec/resources/sshd_config.rb +0 -2
  66. data/lib/inspec/resources/sybase_conf.rb +0 -41
  67. data/lib/inspec/resources/sybase_session.rb +0 -124
  68. data/lib/inspec/utils/deprecated_core_resources_list.rb +0 -25
  69. data/lib/inspec/utils/podman.rb +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '06894dd5c2b09dac3432041d74b257a5b25dd00c9c0a2d623e7343e6a651e1b6'
4
- data.tar.gz: 20592025afc13ecdcae95fcde514b8bc4b5855358e93dcef24365d15aa773eb1
3
+ metadata.gz: 9f295e7d99ec735932c005e949cf99cbb1184780d52eae109cec87faf27501c8
4
+ data.tar.gz: b9fe70883d7593b0d6c7ce51067c7fb2b26154a88981d4ff6458f1f3306518b1
5
5
  SHA512:
6
- metadata.gz: 2d0a1749cfa6f3d1f517f31e5bc722f85ad5ecf8dd4d155df88afcc41c76c93ba21bba3749b534fd1515a724af17bedc706755606a4a3c109a655ef891bc0e0d
7
- data.tar.gz: 2f0b14a4f79fad859d931a8d0427d88306beb44b7b4f8698910a053d383ebc05a28b9b440c7164cfd5f712d56ba08aeb35afa51b21ef1c5f157b4fb525dd2d3c
6
+ metadata.gz: 0f35167f971fcdd9d71a43bf7d6e4baf97ae9f0ac9b16b8ebe33103b15e4fee3892d34f8f2a9c0ef1a499e1860d3a8c2e4080acc20cf5415929c013bc97b0972
7
+ data.tar.gz: cf3bfdfa97c737abcd3f054a6eb4899de5831e2ebebc8a4e9907865835560ff979acb2a86ccce37059dfb0075fc2d628fbdeb3045357a02ad01a9ea22842e462
data/Gemfile CHANGED
@@ -29,12 +29,12 @@ gem "ffi", ">= 1.15.5", "< 1.17.0"
29
29
  # but our runtime dep is still 3.9+
30
30
  gem "rspec", ">= 3.10"
31
31
 
32
- group :omnibus do
33
- gem "rb-readline"
34
- gem "appbundler"
35
- gem "ed25519" # ed25519 ssh key support done here as its a native gem we can't put in the gemspec
36
- gem "bcrypt_pbkdf" # ed25519 ssh key support done here as its a native gem we can't put in the gemspec
37
- end
32
+ # group :omnibus do
33
+ # gem "rb-readline"
34
+ # gem "appbundler"
35
+ # gem "ed25519" # ed25519 ssh key support done here as its a native gem we can't put in the gemspec
36
+ # gem "bcrypt_pbkdf" # ed25519 ssh key support done here as its a native gem we can't put in the gemspec
37
+ # end
38
38
 
39
39
  group :test do
40
40
  gem "chefstyle"
@@ -1,5 +1,5 @@
1
1
  {
2
- "file_version": "1.0.0",
2
+ "file_version": "2.0.0",
3
3
  "unknown_group_action": "ignore",
4
4
  "groups": {
5
5
  "attrs_value_replaces_default": {
@@ -73,11 +73,6 @@
73
73
  "action": "exit",
74
74
  "suffix": "This resource was removed in InSpec 4.0."
75
75
  },
76
- "core_resource_moved_to_rp": {
77
- "action": "warn",
78
- "suffix": "This resource will be moved to a separate resource pack. Additional details will be provided with the InSpec 7 release.",
79
- "comment": "Deprecation notice for core resource which are getting moved to resource packs."
80
- },
81
76
  "resource_iis_website": {
82
77
  "action": "exit",
83
78
  "suffix": "This resource was removed in InSpec 4.0.",
@@ -131,5 +126,19 @@
131
126
  "action": "ignore",
132
127
  "prefix": "The `inspec json` command is deprecated in InSpec 5 and replaced with `inspec export` command."
133
128
  }
129
+ },
130
+ "fallback_resource_packs":{
131
+ "internal_fallback_test.+":{
132
+ "gem":"inspec-test-resources",
133
+ "message":"The internal_fallback_test resource is a test resource used to exercise InSpec's ability to fallback on gem resource packs."
134
+ },
135
+ "elasticsearch.*": {
136
+ "gem":"inspec-elasticsearch-resources",
137
+ "message":"The `inspec-elasticsearch-resources` allows testing of elasticsearch using Inspec."
138
+ },
139
+ "docker.+":{
140
+ "gem": "inspec-docker-resources",
141
+ "message": "The Inspec docker resources are moved out of InSpec core and will be installed as gem"
142
+ }
134
143
  }
135
144
  }
@@ -389,6 +389,9 @@ module Inspec
389
389
  when Inspec::InvalidProfileSignature
390
390
  $stderr.puts exception.message
391
391
  Inspec::UI.new.exit(:bad_signature)
392
+ when Inspec::Exceptions::GemDependencyNotFound
393
+ $stderr.puts exception.message
394
+ Inspec::UI.new.exit(:gem_dependency_not_found)
392
395
  when Inspec::Error
393
396
  $stderr.puts exception.message
394
397
  exit(1)
@@ -52,7 +52,19 @@ module Inspec
52
52
  fetch
53
53
  elsif cache.exists?(cache_key) && !cache.locked?(cache_key)
54
54
  Inspec::Log.debug "Using cached dependency for #{target}"
55
- [cache.prefered_entry_for(cache_key), false]
55
+ cache_value = cache.prefered_entry_for(cache_key)
56
+ if cache_value
57
+ [cache_value, false]
58
+ else
59
+ Inspec::Log.debug "Dependency does not exist in the cache for target #{target}"
60
+ cache_key_name = cache_key
61
+ if cache_key_name.start_with?("gem:")
62
+ # When cache for gem - meaning gemspec exists but gem does not exists then clearing up gemspec is required
63
+ # This logic enables the gem fetcher logic to work step by step again
64
+ Inspec::Log.debug "Clearing cached gemspec to fix dependency issue and enable fresh download."
65
+ FileUtils.rm_rf(cache.gemspec_path_for(cache_key))
66
+ end
67
+ end
56
68
  else
57
69
  begin
58
70
  Inspec::Log.debug "Dependency does not exist in the cache #{target}"
@@ -61,6 +73,7 @@ module Inspec
61
73
  rescue SystemExit => e
62
74
  exit_code = e.status || 1
63
75
  Inspec::Log.error "Error while creating cache for dependency ... #{e.message}"
76
+ # TODO: in the case of gem profile/resource pack dependency installs gone awry, this is the wrong thing to do!
64
77
  FileUtils.rm_rf(cache.base_path_for(fetcher.cache_key))
65
78
  exit(exit_code)
66
79
  ensure
@@ -72,6 +85,8 @@ module Inspec
72
85
  end
73
86
 
74
87
  def assert_cache_sanity!
88
+ # TODO: update this to handle gem resource pack dependencies
89
+ # which are known by a special prefix on their cache key or by having the :gem key
75
90
  return unless target.respond_to?(:key?) && target.key?(:sha256)
76
91
 
77
92
  exception_message = <<~EOF
@@ -45,6 +45,12 @@ module Inspec
45
45
  # For a given name and source_url, return true if the
46
46
  # profile exists in the Cache.
47
47
  #
48
+ # InSpec 7+ Special Magic for Gem-Based Resource Pack Profiles:
49
+ # These "profiles" are installed as gems, and so are "cached"
50
+ # by being installed as gems.
51
+ # The magic is triggered by a special prefix of
52
+ # the cache_key: gem: or gem_path:
53
+ #
48
54
  # @param [String] name
49
55
  # @param [String] source_url
50
56
  # @return [Boolean]
@@ -52,8 +58,21 @@ module Inspec
52
58
  def exists?(key)
53
59
  return false if key.nil? || key.empty?
54
60
 
55
- path = base_path_for(key)
56
- File.directory?(path) || File.exist?("#{path}.tar.gz") || File.exist?("#{path}.zip")
61
+ if key.start_with?("gem:")
62
+ # A gem installation
63
+ (_, gem_name, version) = key.split(":")
64
+ loader = Inspec::Plugin::V2::Loader.new
65
+ !loader.find_gem_directory(gem_name, version).nil?
66
+
67
+ elsif key.start_with?("gem_path:")
68
+ # Gem installed as explicit path reference, as in testing / development
69
+ entry_point_path = key.sub(/^gem_path:/, "")
70
+ File.exist?(entry_point_path)
71
+ else
72
+ # Standard cache entry
73
+ path = base_path_for(key)
74
+ File.directory?(path) || File.exist?("#{path}.tar.gz") || File.exist?("#{path}.zip")
75
+ end
57
76
  end
58
77
 
59
78
  #
@@ -67,8 +86,33 @@ module Inspec
67
86
  # @param [String] source_url
68
87
  # @return [String]
69
88
  #
70
- def base_path_for(cache_key)
71
- File.join(@path, cache_key)
89
+ def base_path_for(key)
90
+ if key.start_with?("gem:")
91
+ # A gem installation
92
+ (_, gem_name, version) = key.split(":")
93
+ loader = Inspec::Plugin::V2::Loader.new
94
+ loader.find_gem_directory(gem_name, version)
95
+
96
+ elsif key.start_with?("gem_path:")
97
+ # Gem installed as explicit path reference, as in testing / development
98
+ entry_point_path = key.sub(/^gem_path:/, "")
99
+ # We were given an explicit path like
100
+ # inspec-test-resources/lib/inspec-test-resources.rb
101
+ # go two directories up
102
+ parts = Pathname(entry_point_path).each_filename.to_a
103
+ File.join(parts.slice(0, parts.length - 2))
104
+ else
105
+ # Standard cache entry
106
+ File.join(@path, key)
107
+ end
108
+ end
109
+
110
+ def gemspec_path_for(key)
111
+ if key.start_with?("gem:")
112
+ (_, gem_name, version) = key.split(":")
113
+ loader = Inspec::Plugin::V2::Loader.new
114
+ loader.find_gemspec_directory(gem_name, version)
115
+ end
72
116
  end
73
117
 
74
118
  #
data/lib/inspec/dsl.rb CHANGED
@@ -2,7 +2,6 @@
2
2
  require "inspec/log"
3
3
  require "inspec/plugin/v2"
4
4
  require "inspec/utils/deprecated_cloud_resources_list"
5
- require "inspec/utils/deprecated_core_resources_list"
6
5
 
7
6
  module Inspec::DSL
8
7
  attr_accessor :backend
@@ -39,20 +38,50 @@ module Inspec::DSL
39
38
  return unless backend
40
39
 
41
40
  begin
42
- include DeprecatedCoreResourcesList
43
- if CORE_RESOURCES_DEPRECATED.include? id
44
- Inspec.deprecate(:core_resource_moved_to_rp, "The resource '#{id}' will not be part of the InSpec 7 core.")
45
- end
46
41
  require "inspec/resources/#{id}"
47
42
  rescue LoadError => e
48
- include DeprecatedCloudResourcesList
49
- cloud_resource = id.start_with?("aws_") ? "aws" : "azure"
50
43
 
51
- # Deprecated AWS and Azure resources in InSpec 5.
52
- if CLOUD_RESOURCES_DEPRECATED.include? id
53
- Inspec.deprecate(:"#{cloud_resource}_resources_in_resource_pack", "Resource '#{id}'")
44
+ # InSpec 7+ Fallback Support for Resource Packs
45
+ # Use Deprecator to lookup the matching gem
46
+ # if a gem matches, try to load the resource from the gem
47
+ gem_name = Inspec::Deprecation::Deprecator.new.match_gem_for_fallback_resource_name(id.to_s)
48
+ if gem_name
49
+ # Install if needed
50
+ cfg = Inspec::Config.cached
51
+ unless cfg.final_options[:auto_install_gems]
52
+ raise Inspec::Plugin::V2::InstallRequiredError, "resource pack gem '#{gem_name}' is required for resource '#{id}' support (consider --auto-install-gems)"
53
+ end
54
+
55
+ Inspec::Plugin::V2::Installer.instance.ensure_installed gem_name
56
+
57
+ # Load the gem, add gemspecs to the path, load any deps, load resource libraries into registry
58
+ # This is plugin API orthodoxy but is impossible to program
59
+ # Inspec::Plugin::V2::Registry.instance.find_activator(gem_name).activate
60
+ loader = Inspec::Plugin::V2::Loader.new
61
+
62
+ # 1. Activate gem and deps.
63
+ loader.activate_managed_gems_for_plugin(gem_name)
64
+
65
+ # 2. Load all libraries from the gem path
66
+ gem_path = loader.find_gem_directory(gem_name)
67
+ resources_path = File.join(gem_path, "lib", gem_name, "resources", "*.rb")
68
+ legacy_library_path = File.join(gem_path, "libraries", "*.rb")
69
+ Dir.glob([resources_path, legacy_library_path]).each do |resource_lib|
70
+ require resource_lib
71
+ end
72
+ # Resources now available in Inspec::Resource.registry
54
73
  else
55
- raise LoadError, "#{e.message}"
74
+ # TODO: InSpec 7: Replace with Fallbacks for inspec-aws-resources and inspec-azure-resources
75
+ include DeprecatedCloudResourcesList
76
+ cloud_resource = id.start_with?("aws_") ? "aws" : "azure"
77
+
78
+ # Deprecated AWS and Azure resources in InSpec 5.
79
+ if CLOUD_RESOURCES_DEPRECATED.include? id
80
+ Inspec.deprecate(:"#{cloud_resource}_resources_in_resource_pack", "Resource '#{id}'")
81
+ else
82
+ # OK, give up entirely
83
+ raise LoadError, "#{e.message}"
84
+ end
56
85
  end
57
86
  end
58
87
 
@@ -7,6 +7,7 @@ module Inspec
7
7
  class ProfileLoadFailed < StandardError; end
8
8
  class ResourceFailed < StandardError; end
9
9
  class ResourceSkipped < StandardError; end
10
+ class GemDependencyNotFound < StandardError; end
10
11
  class SecretsBackendNotFound < ArgumentError; end
11
12
  class ProfileValidationKeyNotFound < ArgumentError; end
12
13
  class ProfileSigningKeyNotFound < ArgumentError; end
@@ -0,0 +1,99 @@
1
+ require "inspec/plugin/v2/installer"
2
+
3
+ module Inspec::Fetcher
4
+ class Gem < Inspec.fetcher(1)
5
+ name "gem"
6
+ priority 0.5 # TODO: verify value
7
+ # Priority is used for setting precedence of fetchers. And registry plugin(v1) decides which fetcher to use for loading profiles by using this priority
8
+ # Gem fetcher's priority should be lowest because gem profiles are only executables via inspec metadata
9
+
10
+ def self.resolve(target)
11
+ if target.is_a?(Hash) && target.key?(:gem)
12
+ resolve_from_hash(target)
13
+ end
14
+ end
15
+
16
+ def self.resolve_from_hash(target)
17
+ return unless target.key?(:gem)
18
+
19
+ new(target)
20
+ end
21
+
22
+ def initialize(target, opts = {})
23
+ @target = target
24
+ @gem_name = target[:gem]
25
+ @version = target[:version] # optional
26
+ @source = target[:source] # optional
27
+ @gem_path = target[:path] # optional, sets local path installation mode
28
+ @backend = opts[:backend]
29
+ @archive_shasum = nil
30
+ end
31
+
32
+ def fetch(path)
33
+ plugin_installer = Inspec::Plugin::V2::Installer.instance
34
+
35
+ # Determine if gem is installed
36
+ have_plugin = false
37
+ Inspec::Log.debug("GemFetcher fetching #{@gem_name} v" + (@version || "ANY"))
38
+
39
+ if @version
40
+ have_plugin = plugin_installer.plugin_version_installed?(@gem_name, @version)
41
+ else
42
+ have_plugin = plugin_installer.plugin_installed?(@gem_name)
43
+ end
44
+
45
+ unless have_plugin
46
+ # Install
47
+ # TODO - error handling?
48
+ Inspec::Log.debug("GemFetcher - install request for #{@gem_name}")
49
+ if @gem_path
50
+ # No version permitted
51
+ plugin_installer.install(@gem_name, path: @gem_path)
52
+ else
53
+ # Passing an extra gem argument to enable detecting gem based plugins
54
+ plugin_installer.install(@gem_name, version: @version, source: @source, gem: @gem_name )
55
+ end
56
+ end
57
+
58
+ # Should the plugin activate? No, it should only be "fetched" (installed)
59
+ # Activation would load resource libararies and would effectively execute the profile
60
+
61
+ @target
62
+ end
63
+
64
+ def archive_path
65
+ @target
66
+ end
67
+
68
+ def writable?
69
+ # Gem based profile is not writable because it is not cached in lockfile
70
+ false
71
+ end
72
+
73
+ def cache_key
74
+ # This special value is interpreted by Inspec::Cache.exists?
75
+ # gem:gemname:version
76
+ # gem_path:/filesystem/path/entrypoint.rb
77
+
78
+ if @gem_path
79
+ "gem_path:#{@gem_path}"
80
+ else
81
+ "gem:#{@gem_name}:#{@version}"
82
+ end
83
+ end
84
+
85
+ # The intent here is to provide a signature that would change with the content of the profile.
86
+ # In the case of gems, for released gems, "name-version" should suffice.
87
+ # For development gems specified by path, the're ever-changing anyway, so just give the path.
88
+ # In eith case, that string is in fact just the cache key.
89
+ def sha256
90
+ cache_key
91
+ end
92
+
93
+ def resolved_source
94
+ h = { gem: @gem_name, version: @version, gem_path: @gem_path }
95
+ h[:sha256] = sha256
96
+ h
97
+ end
98
+ end
99
+ end
@@ -10,7 +10,7 @@ module Inspec::Fetcher
10
10
  if target.is_a?(String)
11
11
  local_path = resolve_from_string(target)
12
12
  new(local_path) if local_path
13
- elsif target.is_a?(Hash)
13
+ elsif target.is_a?(Hash) && !target.key?(:gem)
14
14
  local_path = resolve_from_hash(target)
15
15
  new(local_path, target) if local_path
16
16
  end
@@ -43,4 +43,5 @@ end
43
43
  require "inspec/fetcher/local"
44
44
  require "inspec/fetcher/url"
45
45
  require "inspec/fetcher/git"
46
+ require "inspec/fetcher/gem"
46
47
  require "plugins/inspec-compliance/lib/inspec-compliance/api"
@@ -7,8 +7,12 @@ require "inspec/iaf_file"
7
7
  module Inspec
8
8
  class FileProvider
9
9
  def self.for_path(path)
10
- if path.is_a?(Hash)
10
+ raise "Profile or dependency path not resolved." if path.nil?
11
+
12
+ if path.is_a?(Hash) && !path.key?(:gem)
11
13
  MockProvider.new(path)
14
+ elsif path.is_a?(Hash) && path.key?(:gem)
15
+ GemProvider.new(path)
12
16
  elsif File.directory?(path)
13
17
  DirProvider.new(path)
14
18
  elsif File.exist?(path) && path.end_with?(".tar.gz", "tgz")
@@ -99,6 +103,47 @@ module Inspec
99
103
  end
100
104
  end # class DirProvider
101
105
 
106
+ class GemProvider < FileProvider
107
+ attr_reader :files
108
+ def initialize(gem_info)
109
+ # Determine a path to the gem installation directory
110
+ gem_name = gem_info[:gem]
111
+ bootstrap_file = gem_info[:path]
112
+ gem_version = gem_info[:version]
113
+ if bootstrap_file
114
+ # We were given an explicit path - go two directories up
115
+ parts = Pathname(gem_info[:path]).each_filename.to_a
116
+ @gem_lib_root = File.join(parts.slice(0, parts.length - 2))
117
+ @files = Dir[File.join(Shellwords.shellescape(@gem_lib_root), "**", "*")]
118
+ else
119
+ # Look up where the gem is installed, respecting version
120
+ loader = Inspec::Plugin::V2::Loader.new
121
+ gem_path = loader.find_gem_directory(gem_name, gem_version)
122
+ if gem_path && File.exist?(gem_path)
123
+ gem = DirProvider.new(gem_path)
124
+ @files = gem.files
125
+ gem
126
+ else
127
+ raise Inspec::Exceptions::GemDependencyNotFound, "Dependency does not exist #{gem_name}"
128
+ end
129
+ end
130
+ end
131
+
132
+ def read(file)
133
+ return nil unless files.include?(file)
134
+ return nil unless File.file?(file)
135
+
136
+ File.read(file)
137
+ end
138
+
139
+ def binread(file)
140
+ return nil unless files.include?(file)
141
+ return nil unless File.file?(file)
142
+
143
+ File.binread(file)
144
+ end
145
+ end # class GemProvider
146
+
102
147
  class ZipProvider < FileProvider
103
148
  attr_reader :files
104
149
 
@@ -173,7 +173,7 @@ module Inspec
173
173
  raise ArgumentError, "ERROR: An '=' is required when using --input. Usage: --input input_name1=input_value1 input2=value2"
174
174
  end
175
175
  end
176
- pair = pair.match(/^([^=]+)=(.*)$/)
176
+ pair = pair.match(/(.*?)=(.*)/)
177
177
  input_name, input_value = pair[1], pair[2]
178
178
  input_value = parse_cli_input_value(input_name, input_value)
179
179
  evt = Inspec::Input::Event.new(
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inspec
4
+ module Plugin
5
+ module V2
6
+ # holds all GemSpec related helper functions
7
+ module GemSpecHelper
8
+
9
+ def loaded_recent_most_version_of?(gemspec)
10
+ # Check if the gem is already loaded via bundler
11
+ # In most cases this is true since all Plugins/Resource Packs inherit from inspec-core
12
+ gem_name = gemspec.name
13
+ loaded_gem = Gem.loaded_specs[gem_name]
14
+ return false unless loaded_gem
15
+
16
+ # follow bundler's original philosophy i.e load gems that are recent most
17
+ # This logic works unless there is a pinned version which we don't expect to have since these are managed by us
18
+ if gemspec.version > loaded_gem.version
19
+ # deactivate the lower version specs that are loaded via bundler
20
+ Gem.loaded_specs.delete(gem_name)
21
+ false # so it can re-activate the requested spec
22
+ else
23
+ # don't activate requested gemspec when the already loaded gem is the most recent version
24
+ true
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require "singleton" unless defined?(Singleton)
3
+ require "forwardable" unless defined?(Forwardable)
4
+ require "chef-licensing"
5
+
6
+ module Inspec::Plugin::V2
7
+ class GemSourceManager
8
+ include Singleton
9
+ extend Forwardable
10
+
11
+ DEFAULT_CHEF_RUBY_GEMS_SERVER = "rubygems.chef.io"
12
+ DEFAULT_USERNAME = "v1"
13
+
14
+ def initialize
15
+ @sources = Gem.sources
16
+ end
17
+
18
+ def_delegator :@sources, :sources
19
+
20
+ def add_chef_rubygems_server
21
+ register_source(chef_rubygems_server)
22
+ end
23
+
24
+ def add(sources)
25
+ Array(sources).each { |source| register_source(source) }
26
+ end
27
+
28
+ private
29
+
30
+ def chef_rubygems_server
31
+ "https://#{DEFAULT_USERNAME}:#{licenses_string}@#{DEFAULT_CHEF_RUBY_GEMS_SERVER}"
32
+ end
33
+
34
+ def register_source(source)
35
+ gem_source = Gem::Source.new(source)
36
+ sources << gem_source unless sources.include?(gem_source)
37
+ end
38
+
39
+ def licenses_string
40
+ ChefLicensing.license_keys.join(",")
41
+ end
42
+ end
43
+ end
@@ -12,6 +12,9 @@ require "rubygems/uninstaller"
12
12
  require "rubygems/remote_fetcher"
13
13
 
14
14
  require "inspec/plugin/v2/filter"
15
+ require "inspec/plugin/v2/concerns/gem_spec_helper"
16
+
17
+ require "inspec/plugin/v2/gem_source_manager"
15
18
 
16
19
  module Inspec::Plugin::V2
17
20
  # Handles all actions modifying the user's plugin set:
@@ -23,6 +26,7 @@ module Inspec::Plugin::V2
23
26
  class Installer
24
27
  include Singleton
25
28
  extend Forwardable
29
+ include Inspec::Plugin::V2::GemSpecHelper
26
30
 
27
31
  Gem.configuration["verbose"] = false
28
32
 
@@ -45,6 +49,10 @@ module Inspec::Plugin::V2
45
49
  list_installed_plugin_gems.detect { |spec| spec.name == name && spec.version == Gem::Version.new(version) }
46
50
  end
47
51
 
52
+ def ensure_installed(name)
53
+ plugin_installed?(name) || install(name)
54
+ end
55
+
48
56
  # Installs a plugin. Defaults to assuming the plugin provided is a gem, and will try to install
49
57
  # from whatever gemsources `rubygems` thinks it should use.
50
58
  # If it's a gem, installs it and its dependencies to the `gem_path`. The gem is not activated.
@@ -213,7 +221,10 @@ module Inspec::Plugin::V2
213
221
  if opts.key?(:version) && plugin_version_installed?(plugin_name, opts[:version])
214
222
  raise InstallError, "#{plugin_name} version #{opts[:version]} is already installed."
215
223
  else
216
- raise InstallError, "#{plugin_name} is already installed. Use 'inspec plugin update' to change version."
224
+ # Do not redirect to plugin update when using gem based plugin
225
+ unless gem_based_plugin?(opts)
226
+ raise InstallError, "#{plugin_name} is already installed. Use 'inspec plugin update' to change version."
227
+ end
217
228
  end
218
229
  end
219
230
 
@@ -296,18 +307,21 @@ module Inspec::Plugin::V2
296
307
  install_gem_to_plugins_dir(plugin_dependency, [requested_local_gem_set])
297
308
  end
298
309
 
299
- def install_from_remote_gems(requested_plugin_name, opts)
310
+ def install_from_remote_gems(requested_plugin_name, opts, source_manager: GemSourceManager.instance)
300
311
  plugin_dependency = Gem::Dependency.new(requested_plugin_name, opts[:version] || "> 0")
312
+ source_manager.add_chef_rubygems_server # ensure CHEF RUBYGEMS server is added to the source
313
+
314
+ # This adds custom gem sources to the memoized `Gem.Sources` for this specific run
315
+ # Note: This will not make any change to the environment Gem source list and
316
+ # in fact will consider all of the environment Gem sources and custom gem sources to resolve deps
317
+ source_manager.add(opts[:source])
301
318
 
302
- # BestSet is rubygems.org API + indexing, APISet is for custom sources
303
- sources = if opts[:source]
304
- Gem::Resolver::APISet.new(URI.join(opts[:source] + "/api/v1/dependencies"))
305
- else
306
- Gem::Resolver::BestSet.new
307
- end
319
+ # BestSet is rubygems.org API + indexing by default
320
+ # `Gem.sources` is injected as a dependency implicitly while BestSet is initialized
321
+ sources = Gem::Resolver::BestSet.new
308
322
 
309
323
  begin
310
- install_gem_to_plugins_dir(plugin_dependency, [sources], opts[:update_mode])
324
+ install_gem_to_plugins_dir(plugin_dependency, [sources], opts[:update_mode], opts: opts)
311
325
  rescue Gem::RemoteFetcher::FetchError => gem_ex
312
326
  # TODO: Give a hint if the host was not resolvable or a 404 occured
313
327
  ex = Inspec::Plugin::V2::InstallError.new(gem_ex.message)
@@ -318,7 +332,7 @@ module Inspec::Plugin::V2
318
332
 
319
333
  def install_gem_to_plugins_dir(new_plugin_dependency, # rubocop: disable Metrics/AbcSize
320
334
  extra_request_sets = [],
321
- update_mode = false)
335
+ update_mode = false, opts: {})
322
336
 
323
337
  # Get a list of all the gems available to us.
324
338
  gem_to_force_update = update_mode ? new_plugin_dependency.name : nil
@@ -338,21 +352,28 @@ module Inspec::Plugin::V2
338
352
 
339
353
  # Activate all current plugins before trying to activate the new one
340
354
  loader.list_managed_gems.each do |spec|
341
- next if spec.name == new_plugin_dependency.name && update_mode
355
+ # Skip in case of update mode
356
+ # Skip in case using a gem based plugin
357
+ next if spec.name == new_plugin_dependency.name && (update_mode || gem_based_plugin?(opts))
342
358
 
343
- spec.activate
359
+ # activate the requested gemspec from the Gem::RequestSet
360
+ spec.activate unless loaded_recent_most_version_of?(spec)
344
361
  end
345
362
 
346
363
  # Make sure we remove any previously loaded gem on update
347
- Gem.loaded_specs.delete(new_plugin_dependency.name) if update_mode
364
+ # Make sure we remove any previously loaded gem when trying to use resource pack gem
365
+ # Gem based plugin when updated need to deactivate older version of gem
366
+ Gem.loaded_specs.delete(new_plugin_dependency.name) if update_mode || gem_based_plugin?(opts)
348
367
 
349
368
  # Test activating the solution. This makes sure we do not try to load two different versions
350
369
  # of the same gem on the stack or a malformed dependency.
351
370
  begin
352
371
  solution.each do |activation_request|
353
- unless activation_request.full_spec.activated?
354
- activation_request.full_spec.activate
355
- end
372
+ requested_gemspec = activation_request.full_spec
373
+ next if requested_gemspec.activated?
374
+
375
+ # activate the requested gemspec from the Gem::RequestSet
376
+ requested_gemspec.activate unless loaded_recent_most_version_of?(requested_gemspec)
356
377
  end
357
378
  rescue Gem::LoadError => gem_ex
358
379
  ex = Inspec::Plugin::V2::InstallError.new(gem_ex.message)
@@ -536,5 +557,10 @@ module Inspec::Plugin::V2
536
557
 
537
558
  conf_file
538
559
  end
560
+
561
+ def gem_based_plugin?(opts)
562
+ # Param passed by gem fetcher while installing a new gem plugin dependency.
563
+ !!opts[:gem]
564
+ end
539
565
  end
540
566
  end