inspec 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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -2
  3. data/CHANGELOG.md +42 -19
  4. data/README.md +1 -1
  5. data/Rakefile +16 -3
  6. data/docs/dev/integration-testing.md +31 -0
  7. data/docs/dev/plugins.md +4 -2
  8. data/docs/dsl_inspec.md +104 -4
  9. data/docs/plugins.md +57 -0
  10. data/docs/resources/aws_ebs_volume.md.erb +76 -0
  11. data/docs/resources/aws_ebs_volumes.md.erb +86 -0
  12. data/docs/style.md +178 -0
  13. data/examples/plugins/inspec-resource-lister/Gemfile +12 -0
  14. data/examples/plugins/inspec-resource-lister/LICENSE +13 -0
  15. data/examples/plugins/inspec-resource-lister/README.md +62 -0
  16. data/examples/plugins/inspec-resource-lister/Rakefile +40 -0
  17. data/examples/plugins/inspec-resource-lister/inspec-resource-lister.gemspec +45 -0
  18. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister.rb +16 -0
  19. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister/cli_command.rb +70 -0
  20. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister/plugin.rb +55 -0
  21. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister/version.rb +10 -0
  22. data/examples/plugins/inspec-resource-lister/test/fixtures/README.md +24 -0
  23. data/examples/plugins/inspec-resource-lister/test/functional/README.md +18 -0
  24. data/examples/plugins/inspec-resource-lister/test/functional/inspec_resource_lister_test.rb +110 -0
  25. data/examples/plugins/inspec-resource-lister/test/helper.rb +26 -0
  26. data/examples/plugins/inspec-resource-lister/test/unit/README.md +17 -0
  27. data/examples/plugins/inspec-resource-lister/test/unit/cli_args_test.rb +64 -0
  28. data/examples/plugins/inspec-resource-lister/test/unit/plugin_def_test.rb +51 -0
  29. data/examples/profile/controls/example.rb +9 -8
  30. data/inspec.gemspec +2 -1
  31. data/lib/inspec/attribute_registry.rb +1 -1
  32. data/lib/inspec/globals.rb +4 -0
  33. data/lib/inspec/objects/control.rb +18 -3
  34. data/lib/inspec/plugin/v2.rb +14 -3
  35. data/lib/inspec/plugin/v2/activator.rb +7 -2
  36. data/lib/inspec/plugin/v2/installer.rb +426 -0
  37. data/lib/inspec/plugin/v2/loader.rb +137 -30
  38. data/lib/inspec/plugin/v2/registry.rb +13 -4
  39. data/lib/inspec/profile.rb +2 -1
  40. data/lib/inspec/reporters/json.rb +11 -1
  41. data/lib/inspec/resource.rb +6 -15
  42. data/lib/inspec/rule.rb +18 -9
  43. data/lib/inspec/runner_rspec.rb +1 -1
  44. data/lib/inspec/schema.rb +1 -0
  45. data/lib/inspec/version.rb +1 -1
  46. data/lib/plugins/inspec-plugin-manager-cli/README.md +6 -0
  47. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli.rb +18 -0
  48. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +420 -0
  49. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/plugin.rb +12 -0
  50. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/config_dirs/empty/.gitkeep +0 -0
  51. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/inspec-egg-white-omelette/lib/inspec-egg-white-omelette.rb +2 -0
  52. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/inspec-egg-white-omelette/lib/inspec-egg-white-omelette/.gitkeep +0 -0
  53. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/inspec-wrong-structure/.gitkeep +0 -0
  54. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/wrong-name/lib/wrong-name.rb +1 -0
  55. data/lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/wrong-name/lib/wrong-name/.gitkeep +0 -0
  56. data/lib/plugins/inspec-plugin-manager-cli/test/functional/inspec-plugin_test.rb +651 -0
  57. data/lib/plugins/inspec-plugin-manager-cli/test/unit/cli_args_test.rb +71 -0
  58. data/lib/plugins/inspec-plugin-manager-cli/test/unit/plugin_def_test.rb +20 -0
  59. data/lib/plugins/shared/core_plugin_test_helper.rb +101 -2
  60. data/lib/plugins/things-for-train-integration.rb +14 -0
  61. data/lib/resource_support/aws.rb +2 -0
  62. data/lib/resources/aws/aws_ebs_volume.rb +122 -0
  63. data/lib/resources/aws/aws_ebs_volumes.rb +63 -0
  64. data/lib/resources/port.rb +10 -6
  65. metadata +56 -11
  66. data/docs/ruby_usage.md +0 -204
@@ -14,7 +14,6 @@ module Inspec::Plugin::V2
14
14
  def initialize(options = {})
15
15
  @options = options
16
16
  @registry = Inspec::Plugin::V2::Registry.instance
17
- determine_plugin_conf_file
18
17
  read_conf_file
19
18
  unpack_conf_file
20
19
 
@@ -25,10 +24,17 @@ module Inspec::Plugin::V2
25
24
  # New-style (v2) co-distributed plugins are in lib/plugins,
26
25
  # and may be safely loaded
27
26
  detect_core_plugins unless options[:omit_core_plugins]
27
+
28
+ # Train plugins aren't InSpec plugins (they don't use our API)
29
+ # but InSpec CLI manages them. So, we have to wrap them a bit.
30
+ accommodate_train_plugins
28
31
  end
29
32
 
30
33
  def load_all
31
- registry.each do |plugin_name, plugin_details|
34
+ # Be careful not to actually iterate directly over the registry here;
35
+ # we want to allow "sidecar loading", in which case a plugin may add an entry to the registry.
36
+ registry.plugin_names.dup.each do |plugin_name|
37
+ plugin_details = registry[plugin_name]
32
38
  # We want to capture literally any possible exception here, since we are storing them.
33
39
  # rubocop: disable Lint/RescueException
34
40
  begin
@@ -69,13 +75,44 @@ module Inspec::Plugin::V2
69
75
  end
70
76
  end
71
77
 
78
+ def activate_mentioned_cli_plugins(cli_args = ARGV)
79
+ # Get a list of CLI plugin activation hooks
80
+ registry.find_activators(plugin_type: :cli_command).each do |act|
81
+ next if act.activated?
82
+
83
+ # Decide whether to activate. Several conditions, so split them out for clarity.
84
+ # Assume no, to start. Each condition may flip it true, which will short-circuit
85
+ # all following ||= ops.
86
+ activate_me = false
87
+
88
+ # If the user invoked `inspec help`, activate all CLI plugins, so they can
89
+ # display their usage message.
90
+ activate_me ||= cli_args.first == 'help'
91
+
92
+ # Likewise, if they invoked simply `inspec`, they are confused, and need
93
+ # usage info.
94
+ activate_me ||= cli_args.empty?
95
+
96
+ # If there is anything in the CLI args with the same name, activate it.
97
+ # This is the expected usual activation for individual plugins.
98
+ # `inspec dosomething` => activate the :dosomething hook
99
+ activate_me ||= cli_args.include?(act.activator_name.to_s)
100
+
101
+ # OK, activate.
102
+ if activate_me
103
+ activate(:cli_command, act.activator_name)
104
+ act.implementation_class.register_with_thor
105
+ end
106
+ end
107
+ end
108
+
72
109
  def activate(plugin_type, hook_name)
73
110
  activator = registry.find_activators(plugin_type: plugin_type, activator_name: hook_name).first
74
111
  # We want to capture literally any possible exception here, since we are storing them.
75
112
  # rubocop: disable Lint/RescueException
76
113
  begin
77
114
  impl_class = activator.activation_proc.call
78
- activator.activated = true
115
+ activator.activated?(true)
79
116
  activator.implementation_class = impl_class
80
117
  rescue Exception => ex
81
118
  activator.exception = ex
@@ -84,24 +121,85 @@ module Inspec::Plugin::V2
84
121
  # rubocop: enable Lint/RescueException
85
122
  end
86
123
 
87
- def activate_mentioned_cli_plugins(cli_args = ARGV)
88
- # Get a list of CLI plugin activation hooks
89
- registry.find_activators(plugin_type: :cli_command).each do |act|
90
- next if act.activated
91
- # If there is anything in the CLI args with the same name, activate it
92
- # If the word 'help' appears in the first position, load all CLI plugins
93
- if cli_args.include?(act.activator_name.to_s) || cli_args[0] == 'help' || cli_args.size.zero?
94
- activate(:cli_command, act.activator_name)
95
- act.implementation_class.register_with_thor
96
- end
97
- end
124
+ def plugin_gem_path
125
+ self.class.plugin_gem_path
126
+ end
127
+
128
+ def self.plugin_gem_path
129
+ # I can't believe there isn't a simpler way of getting this
130
+ # 2.4.2.p123 => 2.4.0
131
+ ruby_abi_version = (Gem.ruby_version.segments[0, 2] << 0).join('.')
132
+ File.join(Inspec.config_dir, 'gems', ruby_abi_version)
133
+ end
134
+
135
+ # Lists all gems found in the plugin_gem_path.
136
+ # @return [Array[Gem::Specification]] Specs of all gems found.
137
+ def self.list_managed_gems
138
+ Dir.glob(File.join(plugin_gem_path, 'specifications', '*.gemspec')).map { |p| Gem::Specification.load(p) }
139
+ end
140
+
141
+ def list_managed_gems
142
+ self.class.list_managed_gems
143
+ end
144
+
145
+ # Lists all plugin gems found in the plugin_gem_path.
146
+ # This is simply all gems that begin with train- or inspec-.
147
+ # @return [Array[Gem::Specification]] Specs of all gems found.
148
+ def self.list_installed_plugin_gems
149
+ list_managed_gems.select { |spec| spec.name.match(/^(inspec|train)-/) }
150
+ end
151
+
152
+ def list_installed_plugin_gems
153
+ self.class.list_managed_gems
154
+ end
155
+
156
+ # TODO: refactor the plugin.json file to have its own class, which Loader consumes
157
+ def plugin_conf_file_path
158
+ self.class.plugin_conf_file_path
159
+ end
160
+
161
+ # TODO: refactor the plugin.json file to have its own class, which Loader consumes
162
+ def self.plugin_conf_file_path
163
+ File.join(Inspec.config_dir, 'plugins.json')
98
164
  end
99
165
 
100
166
  private
101
167
 
168
+ # 'Activating' a gem adds it to the load path, so 'require "gemname"' will work.
169
+ # Given a gem name, this activates the gem and all of its dependencies, respecting
170
+ # version pinning needs.
171
+ def activate_managed_gems_for_plugin(plugin_gem_name, version_constraint = '> 0')
172
+ # TODO: enforce first-level version pinning
173
+ plugin_deps = [Gem::Dependency.new(plugin_gem_name.to_s, version_constraint)]
174
+ managed_gem_set = Gem::Resolver::VendorSet.new
175
+ list_managed_gems.each { |spec| managed_gem_set.add_vendor_gem(spec.name, spec.gem_dir) }
176
+
177
+ # TODO: Next two lines merge our managed gems with the other gems available
178
+ # in our "local universe" - which may be the system, or it could be in a Bundler microcosm,
179
+ # or rbenv, etc. Do we want to merge that, though?
180
+ distrib_gem_set = Gem::Resolver::CurrentSet.new
181
+ installed_gem_set = Gem::Resolver.compose_sets(managed_gem_set, distrib_gem_set)
182
+
183
+ # So, given what we need, and what we have available, what activations are needed?
184
+ resolver = Gem::Resolver.new(plugin_deps, installed_gem_set)
185
+ begin
186
+ solution = resolver.resolve
187
+ rescue Gem::UnsatisfiableDependencyError => gem_ex
188
+ # If you broke your install, or downgraded to a plugin with a bad gemspec, you could get here.
189
+ ex = Inspec::Plugin::V2::LoadError.new(gem_ex.message)
190
+ raise ex
191
+ end
192
+ solution.each do |activation_request|
193
+ next if activation_request.full_spec.activated?
194
+ activation_request.full_spec.activate
195
+ # TODO: If we are under Bundler, inform it that we loaded a gem
196
+ end
197
+ end
198
+
102
199
  def annotate_status_after_loading(plugin_name)
103
200
  status = registry[plugin_name]
104
201
  return if status.api_generation == 2 # Gen2 have self-annotating superclasses
202
+ return if status.api_generation == :'train-1' # Train plugins are here as a courtesy, don't poke them
105
203
  case status.installation_type
106
204
  when :bundle
107
205
  annotate_bundle_plugin_status_after_load(plugin_name)
@@ -116,7 +214,7 @@ module Inspec::Plugin::V2
116
214
  status = registry[plugin_name]
117
215
  status.api_generation = 0
118
216
  act = Activator.new
119
- act.activated = true
217
+ act.activated?(true)
120
218
  act.plugin_type = :cli_command
121
219
  act.plugin_name = plugin_name
122
220
  act.activator_name = :default
@@ -133,7 +231,7 @@ module Inspec::Plugin::V2
133
231
  File.join(bundle_dir, 'train-*.rb'),
134
232
  ]
135
233
  Dir.glob(globs).each do |loader_file|
136
- name = File.basename(loader_file, '.rb').gsub(/^(inspec|train)-/, '')
234
+ name = File.basename(loader_file, '.rb').to_sym
137
235
  status = Inspec::Plugin::V2::Status.new
138
236
  status.name = name
139
237
  status.entry_point = loader_file
@@ -143,30 +241,37 @@ module Inspec::Plugin::V2
143
241
  end
144
242
  end
145
243
 
146
- def determine_plugin_conf_file
147
- @plugin_conf_file_path = ENV['INSPEC_CONFIG_DIR'] ? ENV['INSPEC_CONFIG_DIR'] : File.join(Dir.home, '.inspec')
148
- @plugin_conf_file_path = File.join(@plugin_conf_file_path, 'plugins.json')
149
- end
150
-
151
244
  def detect_core_plugins
152
245
  core_plugins_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'plugins'))
153
246
  # These are expected to be organized as proper separate projects,
154
247
  # with lib/ dirs, etc.
155
248
  Dir.glob(File.join(core_plugins_dir, 'inspec-*')).each do |plugin_dir|
156
249
  status = Inspec::Plugin::V2::Status.new
157
- status.name = File.basename(plugin_dir)
158
- status.entry_point = File.join(plugin_dir, 'lib', status.name + '.rb')
159
- status.installation_type = :path
250
+ status.name = File.basename(plugin_dir).to_sym
251
+ status.entry_point = File.join(plugin_dir, 'lib', status.name.to_s + '.rb')
252
+ status.installation_type = :core
160
253
  status.loaded = false
161
254
  registry[status.name.to_sym] = status
162
255
  end
163
256
  end
164
257
 
258
+ def accommodate_train_plugins
259
+ registry.plugin_names.map(&:to_s).grep(/^train-/).each do |train_plugin_name|
260
+ status = registry[train_plugin_name.to_sym]
261
+ status.api_generation = :'train-1'
262
+
263
+ if status.installation_type == :gem
264
+ # Activate the gem. This allows train to 'require' the gem later.
265
+ activate_managed_gems_for_plugin(train_plugin_name)
266
+ end
267
+ end
268
+ end
269
+
165
270
  # TODO: DRY up re: Installer read_or_init_config_file
166
271
  # TODO: refactor the plugin.json file to have its own class, which Loader consumes
167
272
  def read_conf_file
168
- if File.exist?(@plugin_conf_file_path)
169
- @plugin_file_contents = JSON.parse(File.read(@plugin_conf_file_path))
273
+ if File.exist?(plugin_conf_file_path)
274
+ @plugin_file_contents = JSON.parse(File.read(plugin_conf_file_path))
170
275
  else
171
276
  @plugin_file_contents = {
172
277
  'plugins_config_version' => '1.0.0',
@@ -174,19 +279,20 @@ module Inspec::Plugin::V2
174
279
  }
175
280
  end
176
281
  rescue JSON::ParserError => e
177
- raise Inspec::Plugin::V2::ConfigError, "Failed to load plugins JSON configuration from #{@plugin_conf_file_path}:\n#{e}"
282
+ raise Inspec::Plugin::V2::ConfigError, "Failed to load plugins JSON configuration from #{plugin_conf_file_path}:\n#{e}"
178
283
  end
179
284
 
285
+ # TODO: refactor the plugin.json file to have its own class, which Loader consumes
180
286
  def unpack_conf_file
181
287
  validate_conf_file
182
288
  @plugin_file_contents['plugins'].each do |plugin_json|
183
289
  status = Inspec::Plugin::V2::Status.new
184
290
  status.name = plugin_json['name'].to_sym
185
291
  status.loaded = false
186
- status.installation_type = plugin_json['installation_type'].to_sym || :gem
292
+ status.installation_type = (plugin_json['installation_type'] || :gem).to_sym
187
293
  case status.installation_type
188
294
  when :gem
189
- status.entry_point = status.name
295
+ status.entry_point = status.name.to_s
190
296
  status.version = plugin_json['version']
191
297
  when :path
192
298
  status.entry_point = plugin_json['installation_path']
@@ -196,9 +302,10 @@ module Inspec::Plugin::V2
196
302
  end
197
303
  end
198
304
 
305
+ # TODO: refactor the plugin.json file to have its own class, which Loader consumes
199
306
  def validate_conf_file
200
307
  unless @plugin_file_contents['plugins_config_version'] == '1.0.0'
201
- raise Inspec::Plugin::V2::ConfigError, "Unsupported plugins.json file version #{@plugin_file_contents['plugins_config_version']} at #{@plugin_conf_file_path} - currently support versions: 1.0.0"
308
+ raise Inspec::Plugin::V2::ConfigError, "Unsupported plugins.json file version #{@plugin_file_contents['plugins_config_version']} at #{plugin_conf_file_path} - currently support versions: 1.0.0"
202
309
  end
203
310
 
204
311
  plugin_entries = @plugin_file_contents['plugins']
@@ -1,5 +1,7 @@
1
1
  require 'forwardable'
2
2
  require 'singleton'
3
+ require 'train'
4
+
3
5
  require_relative 'status'
4
6
  require_relative 'activator'
5
7
 
@@ -25,11 +27,14 @@ module Inspec::Plugin::V2
25
27
  end
26
28
 
27
29
  def loaded_plugin?(name)
28
- registry.dig(name, :loaded)
30
+ # HACK: Status is normally the source of truth for loadedness, unless it is a train plugin; then the Train::Registry is the source of truth.
31
+ # Also, InSpec registry is keyed on Symbols; Train is keyed on Strings.
32
+ return registry.dig(name.to_sym, :loaded) unless name.to_s.start_with?('train-')
33
+ Train::Plugins.registry.key?(name.to_s.sub(/^train-/, ''))
29
34
  end
30
35
 
31
36
  def loaded_count
32
- registry.values.select(&:loaded).count
37
+ loaded_plugin_names.count
33
38
  end
34
39
 
35
40
  def known_count
@@ -37,7 +42,11 @@ module Inspec::Plugin::V2
37
42
  end
38
43
 
39
44
  def loaded_plugin_names
40
- registry.values.select(&:loaded).map(&:name)
45
+ registry.keys.select { |name| loaded_plugin?(name) }
46
+ end
47
+
48
+ def path_based_plugin?(name)
49
+ known_plugin?(name.to_sym) && registry[name.to_sym].installation_type == :path
41
50
  end
42
51
 
43
52
  def find_status_by_class(klass)
@@ -60,7 +69,7 @@ module Inspec::Plugin::V2
60
69
 
61
70
  def register(name, status)
62
71
  if known_plugin? name
63
- Inspec::Log.warn "PluginLoader: refusing to re-register plugin '#{name}': an existing plugin with that name was loaded via #{existing.installation_type}-loading from #{existing.entry_point}"
72
+ Inspec::Log.debug "PluginLoader: refusing to re-register plugin '#{name}': an existing plugin with that name was loaded via #{registry[name].installation_type}-loading from #{registry[name].entry_point}"
64
73
  else
65
74
  registry[name.to_sym] = status
66
75
  end
@@ -369,7 +369,7 @@ module Inspec
369
369
  error.call(sfile, sline, nil, id, 'Avoid controls with empty IDs') if id.nil? or id.empty?
370
370
  next if id.start_with? '(generated '
371
371
  warn.call(sfile, sline, nil, id, "Control #{id} has no title") if control[:title].to_s.empty?
372
- warn.call(sfile, sline, nil, id, "Control #{id} has no description") if control[:desc].to_s.empty?
372
+ warn.call(sfile, sline, nil, id, "Control #{id} has no descriptions") if control[:descriptions][:default].to_s.empty?
373
373
  warn.call(sfile, sline, nil, id, "Control #{id} has impact > 1.0") if control[:impact].to_f > 1.0
374
374
  warn.call(sfile, sline, nil, id, "Control #{id} has impact < 0.0") if control[:impact].to_f < 0.0
375
375
  warn.call(sfile, sline, nil, id, "Control #{id} has no tests defined") if control[:checks].nil? or control[:checks].empty?
@@ -561,6 +561,7 @@ module Inspec
561
561
  controls[id] = {
562
562
  title: rule.title,
563
563
  desc: rule.desc,
564
+ descriptions: rule.descriptions,
564
565
  impact: rule.impact,
565
566
  refs: rule.ref,
566
567
  tags: rule.tag,
@@ -60,7 +60,8 @@ module Inspec::Reporters
60
60
  control = {
61
61
  id: c[:id],
62
62
  title: c[:title],
63
- desc: c[:desc],
63
+ desc: c.dig(:descriptions, :default),
64
+ descriptions: convert_descriptions(c[:descriptions]),
64
65
  impact: c[:impact],
65
66
  refs: c[:refs],
66
67
  tags: c[:tags],
@@ -116,5 +117,14 @@ module Inspec::Reporters
116
117
  end
117
118
  profiles
118
119
  end
120
+
121
+ def convert_descriptions(data)
122
+ return [] if data.nil?
123
+ results = []
124
+ data.each do |label, text|
125
+ results.push({ label: label.to_s, data: text })
126
+ end
127
+ results
128
+ end
119
129
  end
120
130
  end
@@ -91,21 +91,12 @@ inspec_core_only = !File.exist?(File.join(File.dirname(__FILE__), '..', 'resourc
91
91
 
92
92
  # Do not attempt to load cloud resources if we are in inspec-core mode
93
93
  unless inspec_core_only
94
- # AWS resources are included via their own file,
95
- # but only consider loading them if we have the SDK available, and is v2.
96
- # https://github.com/inspec/inspec/issues/2571
97
- if Gem.loaded_specs.key?('aws-sdk') && Gem.loaded_specs['aws-sdk'].version < Gem::Version.new('3.0.0')
98
- require 'resource_support/aws'
99
- end
100
-
101
- # Azure resources
102
- if Gem.loaded_specs.key?('azure_mgmt_resources')
103
- require 'resources/azure/azure_backend.rb'
104
- require 'resources/azure/azure_generic_resource.rb'
105
- require 'resources/azure/azure_resource_group.rb'
106
- require 'resources/azure/azure_virtual_machine.rb'
107
- require 'resources/azure/azure_virtual_machine_data_disk.rb'
108
- end
94
+ require 'resource_support/aws'
95
+ require 'resources/azure/azure_backend.rb'
96
+ require 'resources/azure/azure_generic_resource.rb'
97
+ require 'resources/azure/azure_resource_group.rb'
98
+ require 'resources/azure/azure_virtual_machine.rb'
99
+ require 'resources/azure/azure_virtual_machine_data_disk.rb'
109
100
  end
110
101
 
111
102
  require 'resources/aide_conf'
data/lib/inspec/rule.rb CHANGED
@@ -32,7 +32,7 @@ module Inspec
32
32
  def initialize(id, profile_id, opts, &block)
33
33
  @impact = nil
34
34
  @title = nil
35
- @desc = nil
35
+ @descriptions = {}
36
36
  @refs = []
37
37
  @tags = {}
38
38
 
@@ -89,9 +89,18 @@ module Inspec
89
89
  @title
90
90
  end
91
91
 
92
- def desc(v = nil)
93
- @desc = unindent(v) unless v.nil?
94
- @desc
92
+ def desc(v = nil, data = nil)
93
+ return @descriptions[:default] if v.nil?
94
+ if data.nil?
95
+ @descriptions[:default] = unindent(v)
96
+ else
97
+ @descriptions[v.to_sym] = unindent(data)
98
+ end
99
+ end
100
+
101
+ def descriptions(description_hash = nil)
102
+ return @descriptions if description_hash.nil?
103
+ @descriptions.merge!(description_hash)
95
104
  end
96
105
 
97
106
  def ref(ref = nil, opts = {})
@@ -221,11 +230,11 @@ module Inspec
221
230
  return
222
231
  end
223
232
  # merge all fields
224
- dst.impact(src.impact) unless src.impact.nil?
225
- dst.title(src.title) unless src.title.nil?
226
- dst.desc(src.desc) unless src.desc.nil?
227
- dst.tag(src.tag) unless src.tag.nil?
228
- dst.ref(src.ref) unless src.ref.nil?
233
+ dst.impact(src.impact) unless src.impact.nil?
234
+ dst.title(src.title) unless src.title.nil?
235
+ dst.descriptions(src.descriptions) unless src.descriptions.nil?
236
+ dst.tag(src.tag) unless src.tag.nil?
237
+ dst.ref(src.ref) unless src.ref.nil?
229
238
 
230
239
  # merge indirect fields
231
240
  # checks defined in the source will completely eliminate
@@ -167,7 +167,7 @@ module Inspec
167
167
  metadata[:profile_id] = ::Inspec::Rule.profile_id(rule)
168
168
  metadata[:impact] = rule.impact
169
169
  metadata[:title] = rule.title
170
- metadata[:desc] = rule.desc
170
+ metadata[:descriptions] = rule.descriptions
171
171
  metadata[:code] = rule.instance_variable_get(:@__code)
172
172
  metadata[:source_location] = rule.instance_variable_get(:@__source_location)
173
173
  end
data/lib/inspec/schema.rb CHANGED
@@ -84,6 +84,7 @@ module Inspec
84
84
  'id' => { 'type' => 'string' },
85
85
  'title' => { 'type' => %w{string null} },
86
86
  'desc' => { 'type' => %w{string null} },
87
+ 'descriptions' => { 'type' => %w{array} },
87
88
  'impact' => { 'type' => 'number' },
88
89
  'refs' => REFS,
89
90
  'tags' => TAGS,