mirror of
https://github.com/inspec/inspec
synced 2025-02-16 22:18:38 +00:00
Merge pull request #5903 from inspec/nm/resource_id-3
CFINSPEC-70 resource_id support take 3
This commit is contained in:
commit
924c0a24c8
17 changed files with 126 additions and 16 deletions
|
@ -423,6 +423,7 @@ The `run_data` object contains all data from the Chef InSpec run. Here is an ove
|
|||
|`run_data.profiles[0].controls[0].results[0].resource`| Undocumented and usually unpopulated; try exploring resource_title |
|
||||
|`run_data.profiles[0].controls[0].results[0].resource_name`| String, name of the resource used in the test|
|
||||
|`run_data.profiles[0].controls[0].results[0].resource_title`| Anonymous Class, the actual instance of the Resource. Responds to to_s with the name of the resource.|
|
||||
|`run_data.profiles[0].controls[0].results[0].resource_title.resource_id`| String, unique identifier of each resource node.|
|
||||
|`run_data.profiles[0].controls[0].results[0].run_time`| Float, execution time in seconds for the test|
|
||||
|`run_data.profiles[0].controls[0].results[0].skip_message`| String, if the test was skipped, explains why (user provided)|
|
||||
|`run_data.profiles[0].controls[0].results[0].start_time`| DateTime, time the test started executing|
|
||||
|
|
|
@ -54,6 +54,17 @@ The following methods are available to the resource:
|
|||
- inspec - Contains a registry of all other resources to interact with the operating system or target in general.
|
||||
- skip_resource - A resource may call this method to indicate that requirements aren't met. All tests that use this resource will be marked as skipped.
|
||||
|
||||
The additional methods may be defined within the resource:
|
||||
|
||||
- resource_id - An instance method. Place logic here to determine the unique identifier for a resource, and set it using the superclass method. Following is an example of its usage in an InSpec test:
|
||||
|
||||
```
|
||||
# example_config resource can have unique conf file path as an identifier.
|
||||
describe example_config do
|
||||
its("resource_id") { should eq PATH_OF_CONF_FILE }
|
||||
end
|
||||
```
|
||||
|
||||
The following example shows a full resource using attributes and methods
|
||||
to provide simple access to a configuration file:
|
||||
|
||||
|
@ -88,6 +99,11 @@ class ExampleConfig < Inspec.resource(1)
|
|||
@params[name]
|
||||
end
|
||||
|
||||
def resource_id
|
||||
value = example_method_to_determine_resource_id # define logic to determine resource_id value
|
||||
super(value)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def read_content
|
||||
|
|
|
@ -36,21 +36,41 @@ module Inspec::Reporters
|
|||
def profile_results(control)
|
||||
(control[:results] || []).map { |r|
|
||||
{
|
||||
status: r[:status],
|
||||
code_desc: r[:code_desc],
|
||||
run_time: r[:run_time],
|
||||
start_time: r[:start_time],
|
||||
resource: r[:resource],
|
||||
skip_message: r[:skip_message],
|
||||
message: r[:message],
|
||||
exception: r[:exception],
|
||||
backtrace: r[:backtrace],
|
||||
resource_class: r[:resource_class],
|
||||
status: r[:status],
|
||||
code_desc: r[:code_desc],
|
||||
run_time: r[:run_time],
|
||||
start_time: r[:start_time],
|
||||
resource: r[:resource],
|
||||
skip_message: r[:skip_message],
|
||||
message: r[:message],
|
||||
exception: r[:exception],
|
||||
backtrace: r[:backtrace],
|
||||
resource_class: r[:resource_class],
|
||||
resource_params: r[:resource_params].to_s,
|
||||
resource_id: extract_resource_id(r),
|
||||
}.reject { |_k, v| v.nil? }
|
||||
}
|
||||
end
|
||||
|
||||
def extract_resource_id(r)
|
||||
# According to the RunData API, this is supposed to be an anonymous
|
||||
# class that represents a resource, with embedded instance methods....
|
||||
resource_obj = r[:resource_title]
|
||||
return resource_obj.resource_id if resource_obj.respond_to?(:resource_id)
|
||||
|
||||
# But sometimes, it isn't, and has been collapsed into the to_s stringification of the resource.
|
||||
if resource_obj.is_a?(String)
|
||||
orig_str = resource_obj
|
||||
# Try to trim off the resource class - eg "File /some/path" => "/some/path"
|
||||
trimmed_str = orig_str.sub(/^#{r[:resource_class]}/i, "").strip
|
||||
trimmed_str.empty? ? orig_str : trimmed_str
|
||||
else
|
||||
# Boo, InSpec is crazy, and we don't know what it possibly could be.
|
||||
# Failsafe for resource_id is empty string.
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def profiles
|
||||
run_data[:profiles].map do |p|
|
||||
res = {
|
||||
|
|
|
@ -34,6 +34,12 @@ module Inspec
|
|||
Inspec::Resource.support_registry[key].push(criteria)
|
||||
end
|
||||
|
||||
def resource_id(value = nil)
|
||||
@resource_id = value if value
|
||||
@resource_id = "" if @resource_id.nil?
|
||||
@resource_id
|
||||
end
|
||||
|
||||
# TODO: this is pretty terrible and is only here to work around
|
||||
# the idea that we've trained resource authors to make initialize
|
||||
# methods w/o calling super.
|
||||
|
|
|
@ -57,6 +57,7 @@ module Inspec
|
|||
"run_time" => { "type" => "number" },
|
||||
"start_time" => { "type" => "string" },
|
||||
"resource_class" => { "type" => "string", "optional" => true },
|
||||
"resource_id" => { "type" => "string", "optional" => true },
|
||||
"skip_message" => { "type" => "string", "optional" => true },
|
||||
"resource" => { "type" => "string", "optional" => true },
|
||||
"message" => { "type" => "string", "optional" => true },
|
||||
|
|
|
@ -38,6 +38,7 @@ module Inspec
|
|||
"message" => Primitives.desc(Primitives::STRING, "An explanation of the test status - usually only provided when the test fails."),
|
||||
"skip_message" => Primitives.desc(Primitives::STRING, "An explanation of the test status if the status was 'skipped."),
|
||||
"exception" => Primitives.desc(Primitives::STRING, "The type of exception if an exception was thrown."),
|
||||
"resource_id" => Primitives.desc(Primitives::STRING, "The unique identifier of the resource."),
|
||||
"backtrace" => {
|
||||
"anyOf" => [
|
||||
Primitives.array(Primitives::STRING),
|
||||
|
|
5
test/fixtures/profiles/resource_ids/controls/basic.rb
vendored
Normal file
5
test/fixtures/profiles/resource_ids/controls/basic.rb
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
control "basic custom resource" do
|
||||
describe basic do
|
||||
its("resource_id") { should cmp "42" }
|
||||
end
|
||||
end
|
10
test/fixtures/profiles/resource_ids/inspec.yml
vendored
Normal file
10
test/fixtures/profiles/resource_ids/inspec.yml
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: resource_ids
|
||||
title: InSpec Profile
|
||||
maintainer: The Authors
|
||||
copyright: The Authors
|
||||
copyright_email: you@example.com
|
||||
license: Apache-2.0
|
||||
summary: An InSpec Compliance Profile
|
||||
version: 0.1.0
|
||||
supports:
|
||||
platform: os
|
7
test/fixtures/profiles/resource_ids/libraries/basic_resource.rb
vendored
Normal file
7
test/fixtures/profiles/resource_ids/libraries/basic_resource.rb
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
class Basic < Inspec.resource(1)
|
||||
name 'basic'
|
||||
|
||||
def resource_id
|
||||
super("42")
|
||||
end
|
||||
end
|
9
test/fixtures/reporters/json_merged_output
vendored
9
test/fixtures/reporters/json_merged_output
vendored
|
@ -62,7 +62,8 @@
|
|||
"code_desc": "File /etc/hosts mode should eq 420",
|
||||
"run_time": 0.031503,
|
||||
"start_time": "2018-07-30T08:56:41-04:00",
|
||||
"resource_params": ""
|
||||
"resource_params": "",
|
||||
"resource_id": "File /etc/hosts"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -103,7 +104,8 @@
|
|||
"code_desc": "File /etc/passwd should exist",
|
||||
"run_time": 0.003954,
|
||||
"start_time": "2018-07-30T08:56:41-04:00",
|
||||
"resource_params": ""
|
||||
"resource_params": "",
|
||||
"resource_id": "File /etc/passwd"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -134,7 +136,8 @@
|
|||
"start_time": "2018-07-30T08:56:41-04:00",
|
||||
"resource": "Operating System Detection",
|
||||
"skip_message": "Skipped control due to only_if condition.",
|
||||
"resource_params": ""
|
||||
"resource_params": "",
|
||||
"resource_id": "Operating System Detection"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
2
test/fixtures/reporters/json_output
vendored
2
test/fixtures/reporters/json_output
vendored
|
@ -1 +1 @@
|
|||
{"platform":{"name":"mac_os_x","release":"17.2.0"},"profiles":[{"name":"long_commands","version":"0.1.0","sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","title":"InSpec Profile","maintainer":"The Authors","summary":"An InSpec Compliance Profile","license":"Apache-2.0","copyright":"The Authors","copyright_email":"you@example.com","supports":[{"os-family":"bds"},{"os-name":"mac_os_x","release":"17.*"}],"attributes":[],"groups":[{"id":"controls/example.rb","controls":["(generated from example.rb:7 871cd54043069c5c4f6e382fd5627830)","tmp-1.0","(generated from example.rb:21 2ff474c5357e7070f4c3efa932032dcb)"],"title":"sample section"},{"id":"controls/run_command.rb","controls":["(generated from run_command.rb:5 a411d4ded1530b2f48170840e1127584)"]}],"controls":[{"id":"(generated from example.rb:7 871cd54043069c5c4f6e382fd5627830)","title":null,"desc":null,"descriptions":[],"impact":0.5,"refs":[],"tags":{},"code":"","source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"waiver_data":{},"results":[{"status":"passed","code_desc":"File /tmp should be directory","run_time":0.002058,"start_time":"2018-01-05 11:43:04 -0500","resource_params":""}]},{"id":"tmp-1.0","title":"Create /tmp directory","desc":"An optional description...","descriptions":[{"label":"default","data":"An optional description..."}],"impact":0.7,"refs":[],"tags":{},"code":"control 'tmp-1.0' do # A unique ID for this control\n impact 0.7 # The criticality, if this control fails.\n title 'Create /tmp directory' # A human-readable title\n desc 'An optional description...'\n describe file('/tmp') do # The actual test\n it { should be_directory }\n end\nend\n","waiver_data":{},"source_location":{"line":12,"ref":"../inspec-demo/_test/long_commands/controls/example.rb"},"results":[{"status":"passed","code_desc":"File /tmp should be directory","run_time":0.000102,"start_time":"2018-01-05 11:43:04 -0500","resource_params":""}]},{"id":"(generated from example.rb:21 2ff474c5357e7070f4c3efa932032dcb)","title":null,"desc":null,"descriptions":[],"impact":0.5,"refs":[],"tags":{},"code":"","waiver_data":{},"source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"results":[{"status":"failed","code_desc":"gem package rubocop should be installed","run_time":0.000168,"start_time":"2018-01-05 11:43:04 -0500","message":"rubocop is not installed","resource_params":""}]},{"id":"(generated from run_command.rb:5 a411d4ded1530b2f48170840e1127584)","title":null,"desc":null,"descriptions":[],"impact":0.5,"refs":[],"tags":{},"code":"","waiver_data":{},"source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"results":[{"status":"passed","code_desc":"Command whoami stdout should eq \"jquick\\n\"","run_time":0.034938,"start_time":"2018-01-05 11:43:04 -0500","resource_params":""}]}]}],"statistics":{"duration":0.039182},"version":"1.49.2"}
|
||||
{"platform":{"name":"mac_os_x","release":"17.2.0"},"profiles":[{"name":"long_commands","version":"0.1.0","sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","title":"InSpec Profile","maintainer":"The Authors","summary":"An InSpec Compliance Profile","license":"Apache-2.0","copyright":"The Authors","copyright_email":"you@example.com","supports":[{"os-family":"bds"},{"os-name":"mac_os_x","release":"17.*"}],"attributes":[],"groups":[{"id":"controls/example.rb","controls":["(generated from example.rb:7 871cd54043069c5c4f6e382fd5627830)","tmp-1.0","(generated from example.rb:21 2ff474c5357e7070f4c3efa932032dcb)"],"title":"sample section"},{"id":"controls/run_command.rb","controls":["(generated from run_command.rb:5 a411d4ded1530b2f48170840e1127584)"]}],"controls":[{"id":"(generated from example.rb:7 871cd54043069c5c4f6e382fd5627830)","title":null,"desc":null,"descriptions":[],"impact":0.5,"refs":[],"tags":{},"code":"","source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"waiver_data":{},"results":[{"status":"passed","code_desc":"File /tmp should be directory","run_time":0.002058,"start_time":"2018-01-05 11:43:04 -0500","resource_params":"","resource_id":"File /tmp"}]},{"id":"tmp-1.0","title":"Create /tmp directory","desc":"An optional description...","descriptions":[{"label":"default","data":"An optional description..."}],"impact":0.7,"refs":[],"tags":{},"code":"control 'tmp-1.0' do # A unique ID for this control\n impact 0.7 # The criticality, if this control fails.\n title 'Create /tmp directory' # A human-readable title\n desc 'An optional description...'\n describe file('/tmp') do # The actual test\n it { should be_directory }\n end\nend\n","waiver_data":{},"source_location":{"line":12,"ref":"../inspec-demo/_test/long_commands/controls/example.rb"},"results":[{"status":"passed","code_desc":"File /tmp should be directory","run_time":0.000102,"start_time":"2018-01-05 11:43:04 -0500","resource_params":"","resource_id":"File /tmp"}]},{"id":"(generated from example.rb:21 2ff474c5357e7070f4c3efa932032dcb)","title":null,"desc":null,"descriptions":[],"impact":0.5,"refs":[],"tags":{},"code":"","waiver_data":{},"source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"results":[{"status":"failed","code_desc":"gem package rubocop should be installed","run_time":0.000168,"start_time":"2018-01-05 11:43:04 -0500","message":"rubocop is not installed","resource_params":"","resource_id":"gem package rubocop"}]},{"id":"(generated from run_command.rb:5 a411d4ded1530b2f48170840e1127584)","title":null,"desc":null,"descriptions":[],"impact":0.5,"refs":[],"tags":{},"code":"","waiver_data":{},"source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"results":[{"status":"passed","code_desc":"Command whoami stdout should eq \"jquick\\n\"","run_time":0.034938,"start_time":"2018-01-05 11:43:04 -0500","resource_params":"","resource_id":"stdout"}]}]}],"statistics":{"duration":0.039182},"version":"1.49.2"}
|
||||
|
|
1
test/fixtures/reporters/yaml_output
vendored
1
test/fixtures/reporters/yaml_output
vendored
|
@ -31,6 +31,7 @@
|
|||
:run_time: 0.001313935
|
||||
:start_time: '2018-05-31T16:22:19+05:30'
|
||||
:resource_params: ''
|
||||
:resource_id: ''
|
||||
:statistics:
|
||||
:duration: 0.002678506
|
||||
:version: 2.1.83
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
:run_time: 0.001313935
|
||||
:start_time: '2018-05-31T16:22:19+05:30'
|
||||
:resource_params: ''
|
||||
:resource_id: ''
|
||||
:statistics:
|
||||
:duration: 0.002678506
|
||||
:version: 2.1.83
|
||||
|
|
|
@ -1299,4 +1299,13 @@ EOT
|
|||
|
||||
end
|
||||
end
|
||||
|
||||
describe "when evalutating profiles that reference resource_id" do
|
||||
let(:run_result) { run_inspec_process("exec #{profile}", json: true) }
|
||||
let(:profile) { "#{profile_path}/resource_ids" } # A profile with custom resources and test controls that exercise resource ids
|
||||
it "should evaluate all test controls correctly" do
|
||||
_(run_result.stderr).must_be_empty
|
||||
assert_json_controls_passing
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,4 +19,12 @@ describe "inspec schema" do
|
|||
assert_exit_code 0, out
|
||||
end
|
||||
end
|
||||
|
||||
describe "validate schema of exec-json" do
|
||||
it "contains resource_id key" do
|
||||
out = inspec("schema exec-json")
|
||||
json_output = JSON.parse(out.stdout)
|
||||
_(json_output["definitions"]["Control_Result"]["properties"]["resource_id"]).wont_be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,6 +56,7 @@ describe Inspec::Reporters::Json do
|
|||
run_time: 0.002058,
|
||||
start_time: "2018-01-05 11:43:04 -0500",
|
||||
resource_params: "",
|
||||
resource_id: "File /tmp",
|
||||
}
|
||||
result = report.send(:profile_results, control)
|
||||
_(result.first).must_equal hash
|
||||
|
@ -70,6 +71,7 @@ describe Inspec::Reporters::Json do
|
|||
run_time: 0.002058,
|
||||
start_time: "2018-01-05 11:43:04 -0500",
|
||||
resource_params: "",
|
||||
resource_id: "File /tmp",
|
||||
resource: "File",
|
||||
skip_message: "skipping",
|
||||
}
|
||||
|
|
|
@ -44,12 +44,12 @@ describe Inspec::Resource do
|
|||
end
|
||||
|
||||
describe "#example" do
|
||||
it "will register a description" do
|
||||
it "will register a example" do
|
||||
expected = rand.to_s
|
||||
_(create { example expected }.example).must_equal expected
|
||||
end
|
||||
|
||||
it "can change the description" do
|
||||
it "can change the example" do
|
||||
c = create { example rand.to_s }
|
||||
c.example(x = rand.to_s)
|
||||
_(c.example).must_equal x
|
||||
|
@ -94,4 +94,23 @@ describe Inspec::Resource do
|
|||
Inspec::Resource.support_registry["os"] = nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "resource_id" do
|
||||
it "can set instance variable resource_id and use it" do
|
||||
cls = create {}
|
||||
cls_obj = cls.new(nil, "notSoRandomName")
|
||||
cls_obj.resource_id(x = rand.to_s)
|
||||
_(cls_obj.resource_id).must_equal x
|
||||
_(cls_obj.instance_variable_get("@resource_id")).must_equal x
|
||||
end
|
||||
|
||||
it "can change the resource_id value and use it" do
|
||||
cls = create {}
|
||||
cls_obj = cls.new(nil, "notSoRandomName")
|
||||
cls_obj.resource_id(x = rand.to_s)
|
||||
_(cls_obj.resource_id).must_equal x
|
||||
cls_obj.resource_id(y = rand.to_s)
|
||||
_(cls_obj.resource_id).must_equal y
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue