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
@@ -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'
@@ -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
@@ -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,