mirror of
https://github.com/inspec/inspec
synced 2025-02-16 22:18:38 +00:00
Add common methods to FilterTable automatically (#3104)
Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
parent
f19352c211
commit
f9dd82f2f6
12 changed files with 330 additions and 58 deletions
|
@ -43,18 +43,23 @@ A resource class using FilterTable typically will call a sequence similar to thi
|
|||
|
||||
```
|
||||
filter = FilterTable.create
|
||||
filter.register_filter_method(:entries)
|
||||
.register_custom_property(:exists?) { |x| !x.entries.empty? }
|
||||
.register_custom_property(:count) { |x| x.entries.count }
|
||||
.register_custom_property(:thing_ids, field: :thing_id)
|
||||
filter.install_filter_methods_on_resource(self, :table)
|
||||
.register_column(:thing_ids, field: :thing_id)
|
||||
.install_filter_methods_on_resource(self, :table)
|
||||
```
|
||||
|
||||
Each of those calls supports method chaining.
|
||||
Legacy code might look like this:
|
||||
```
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
.add(:count) { |x| x.entries.count }
|
||||
.add(:thing_ids, field: :thing_id)
|
||||
.connect(self, :table)
|
||||
```
|
||||
|
||||
### create
|
||||
|
||||
Returns a blank instance of a FilterTable::Factory.
|
||||
Returns a blank instance of a FilterTable::Factory. It also adds a default implementation of `where` `raw_data`, and `entries` using `register_filter_method`, `count` using `register_custom_property`, and `exist?` using `register_custom_matcher`.
|
||||
|
||||
### register\_filter\_method
|
||||
|
||||
|
@ -88,7 +93,7 @@ The implementation of the custom property method is generated by `create_custom_
|
|||
|
||||
#### Behavior when a block is provided
|
||||
|
||||
This behavior is implemented by lines 388-394.
|
||||
This behavior is implemented by lines 398-404.
|
||||
|
||||
If a block is provided, it is turned into a Lambda and used as the method body.
|
||||
|
||||
|
@ -117,7 +122,7 @@ If you provide _both_ a block and opts, only the block is used, and the options
|
|||
|
||||
#### Behavior when no block is provided
|
||||
|
||||
If you do not provide a block, you _must_ provide a `:field` option (though that does no appear to be enforced). The behavior is to define a method with the name provided, that has a conditional return type. The method body is defined in lines 396-413.
|
||||
If you do not provide a block, you _must_ provide a `:field` option (though that does no appear to be enforced). The behavior is to define a method with the name provided, that has a conditional return type. The method body is defined in lines 306-423.
|
||||
|
||||
If called without arguments, it returns an array of the values in the raw data for that column.
|
||||
```
|
||||
|
@ -144,7 +149,7 @@ You can provide options to `register_custom_property` / `add`, after the desired
|
|||
|
||||
This is the most common option, and is mandatory if a block is not provided. It selects an implementation in which the desired method will be defined such that it returns an array of the row values using the specified key. In other words, this acts as a "column fetcher", like in SQL: "SELECT some_column FROM some_table"
|
||||
|
||||
Internally, (line 269-276), a Struct type is created to represent a row of raw data. The struct's attribute list is taken from the `field` options passed to `register_custom_property` / `add`. This new type is stored as `row_eval_context_type`. It is used as the evaluation context for block-mode `where` calls.
|
||||
Internally, (line 271-278), a Struct type is created to represent a row of raw data. The struct's attribute list is taken from the `field` options passed to `register_custom_property` / `add`. This new type is stored as `row_eval_context_type`. It is used as the evaluation context for block-mode `where` calls.
|
||||
|
||||
* No checking is performed to see if the field name is actually a column in the raw data (the raw data hasn't been fetched yet, so we can't check).
|
||||
* You can't have two `register_custom_property` / `add` calls that reference the same field, because the Struct would see that as a duplicate attribute.
|
||||
|
@ -153,7 +158,7 @@ POSSIBLE BUG: We could deduplicate the field names when defining the Struct, thu
|
|||
|
||||
##### style
|
||||
|
||||
The `style` option is intended to effect post-processing of the return value from the generated method. To date there is only one recognized value, `:simple`, which `flatten`s, `uniq`s, and `compact`s the array value of the property. This is implemented on line 406.
|
||||
The `style` option is intended to effect post-processing of the return value from the generated method. To date there is only one recognized value, `:simple`, which `flatten`s, `uniq`s, and `compact`s the array value of the property. This is implemented on line 416.
|
||||
|
||||
No other values for `:style` have been seen.
|
||||
|
||||
|
@ -183,7 +188,7 @@ So, `install_filter_methods_on_resource`/`connect`'s job is to actually install
|
|||
|
||||
#### Defines a special Struct type to support block-mode where
|
||||
|
||||
At lines 270-276, a new Struct type `row_eval_context_type` is defined, with attributes for each of the known table fields. The motivation for this struct type is to implement the block-mode behavior of `where`. Because each struct represents a row, and it has the attributes (accessors) for the fields, block-mode `where` is implemented by `instance_eval`ing against each row as a struct.
|
||||
At lines 270-278, a new Struct type `row_eval_context_type` is defined, with attributes for each of the known table fields. We're careful to filter out any fields that have a block implementation, because they cannot be accessed as a row value, and so should not be present on the row context. The motivation for this struct type is to implement the block-mode behavior of `where`. Because each struct represents a row, and it has the attributes (accessors) for the fields, block-mode `where` is implemented by `instance_eval`ing against each row as a struct.
|
||||
|
||||
Additionally, an instance variable, `@criteria_string` is defined, with an accessor. `to_s` is implemented, using `@criteria_string`, or `super` if not defined. I guess we then rely on the `Struct` class to stringify.
|
||||
|
||||
|
@ -197,41 +202,41 @@ BUG: this means that any `register_custom_property` / `add` call that uses a blo
|
|||
|
||||
#### Re-pack the "connectors"
|
||||
|
||||
On lines 278-280, the list of custom properties ("connectors", registered using the `register_custom_property` / `add` method) are repacked into an array of hashes of two elements - the desired method name and the lambda that will be used as the method body. The lambda is created by the private method `create_custom_property_body`; see `register_custom_property` for discussion about how the implementation behaves.
|
||||
On lines 280-282, the list of custom properties ("connectors", registered using the `register_custom_property` / `add` method) are repacked into an array of hashes of two elements - the desired method name and the lambda that will be used as the method body. The lambda is created by the private method `create_custom_property_body`; see `register_custom_property` for discussion about how the implementation behaves.
|
||||
|
||||
#### Subclass FilterTable::Table into an anonymous class
|
||||
|
||||
At line 284, create the local var `table_class`, which refers to an anonymous class that subclasses FilterTable::Table. The class is opened and two groups of methods are defined.
|
||||
At line 286, create the local var `table_class`, which refers to an anonymous class that subclasses FilterTable::Table. The class is opened and two groups of methods are defined.
|
||||
|
||||
Lines 286-288 install the "custom_property" methods, using the names and lambdas determined on line 279.
|
||||
Lines 288-290 install the "custom_property" methods, using the names and lambdas determined on line 281.
|
||||
|
||||
Lines 290-292 allow the Table subclass to introspect on the CustomProperties by slipping a reference to it in the class body, forming a closure.
|
||||
Lines 292-294 allow the Table subclass to introspect on the CustomProperties by slipping a reference to it in the class body, forming a closure.
|
||||
|
||||
Line 295-301 define a method, `create_eval_context_for_row`. This is used when executing a block-mode `where`; see line 120.
|
||||
Line 296-303 define a method, `create_eval_context_for_row`. This is used when executing a block-mode `where`; see line 120.
|
||||
|
||||
#### Setup the row context struct for lazy loading
|
||||
|
||||
If you have a lazy field named `color` and it has not yet been populated, we need to trigger it to populate the first time it is read. If a block-mode where is used (`my_resource.where { color == :red }`), then we have to intercept the Struct's default `getter method`, and call the lazy column's lambda.
|
||||
|
||||
Lines 304-327 do exactly that, by defining methods on the Struct subclass we're using for context. We continue to rely on the default Struct setter (`[]=`) and getter at the end (`[]`).
|
||||
Lines 306-329 do exactly that, by defining methods on the Struct subclass we're using for context. We continue to rely on the default Struct setter (`[]=`) and getter at the end (`[]`).
|
||||
|
||||
#### Install methods on the resource
|
||||
|
||||
Lines 335-346 install the "filter_methods" and "custom properties" methods onto the resource that you are authoring.
|
||||
Lines 337-348 install the "filter_methods" and "custom properties" methods onto the resource that you are authoring.
|
||||
|
||||
Line 335-336 collects the names of the methods to define - by agglomerating the names of the "filter_methods" and "custom properties" methods. They are treated the same.
|
||||
Line 337-338 collects the names of the methods to define - by agglomerating the names of the "filter_methods" and "custom properties" methods. They are treated the same.
|
||||
|
||||
Line 337 uses `send` with a block to call `define_method` on the resource class that you're authoring. Using a block with `send` is undocumented, but is treated as an implicit argument (per StackOverflow) , so the end result is that the block is used as the body for the new method being defined.
|
||||
Line 339 uses `send` with a block to call `define_method` on the resource class that you're authoring. Using a block with `send` is undocumented, but is treated as an implicit argument (per StackOverflow) , so the end result is that the block is used as the body for the new method being defined.
|
||||
|
||||
The method body is wrapped in an exception-catching facility that catches skipped or failed resource exceptions and wraps them in a specialized exception catcher class. TBD: understand this better.
|
||||
|
||||
Line 340 constructs an instance of the anonymous FilterTable::Table subclass defined at 284. It passes three args:
|
||||
Line 342 constructs an instance of the anonymous FilterTable::Table subclass defined at 284. It passes three args:
|
||||
|
||||
1. `self`. A reference to the resource instance.
|
||||
2. The return value of calling the data fetcher method (that is an array of hashes, the raw data).
|
||||
3. The string ' with', which is probably informing the criteria stringification. The extra space is intentional, as it follows the resource name: 'my_things with color == :red' might be a result.
|
||||
|
||||
On line 341, we then immediately call a method on that "FilterTable::Table subclass instance". The method name is the same as the one we're defining on the resource - but we're calling it on the Table. Recall we defined all the "custom_property" methods on the Table subclass at line 286-288. The method gets called with any args or block passed, and since it's the last thing, it provides the return value.
|
||||
On line 343, we then immediately call a method on that "FilterTable::Table subclass instance". The method name is the same as the one we're defining on the resource - but we're calling it on the Table. Recall we defined all the "custom_property" methods on the Table subclass at line 288-290. The method gets called with any args or block passed, and since it's the last thing, it provides the return value.
|
||||
|
||||
## What is its behavior? (FilterTable::Table)
|
||||
|
||||
|
@ -272,7 +277,7 @@ We know from the above exploration of `install_filter_methods_on_resource` / `co
|
|||
### FilterTable::Table constructor and internals
|
||||
|
||||
Factory calls the FilterTable::Table constructor at 87-93 with three args. Table stores them into instance vars:
|
||||
* @resource_instance - this was passed in as `self` from line 340
|
||||
* @resource_instance - this was passed in as `self` from line 342
|
||||
* @raw_data - an array of hashes
|
||||
* @criteria_string - This looks to be stringification trace data; the string ' with' was passed in by Factory.
|
||||
* @populated_lazy_columns = a hash, by lazy field name, with boolean values. This is set true if `populate_lazy_field` is called on a field.
|
||||
|
@ -285,13 +290,13 @@ From usage, I expect entries to return a structure that resembles an array of ha
|
|||
|
||||
#### A new method `entries` is defined on the resource class
|
||||
|
||||
That is performed by Factory#connect line 337.
|
||||
That is performed by Factory#connect line 339.
|
||||
|
||||
#### It delegates to FilterTable::Table#entries
|
||||
|
||||
This is a real method defined in filter.rb line 155.
|
||||
|
||||
It loops over the provided raw data (@raw_data) and builds an array, calling `create_eval_context_for_row` (see Factory lines 295-301) on each row; also appending a stringification trace to each entry. The array is returned.
|
||||
It loops over the provided raw data (@raw_data) and builds an array, calling `create_eval_context_for_row` (see Factory lines 297-303) on each row; also appending a stringification trace to each entry. The array is returned.
|
||||
|
||||
#### `entries` conclusion
|
||||
|
||||
|
@ -309,7 +314,7 @@ So, what happens when you call `register_filter_method(:where)` and then call `r
|
|||
|
||||
#### A new method `where` is defined on the resource class
|
||||
|
||||
That is performed by Factory#connect line 337.
|
||||
That is performed by Factory#connect line 339.
|
||||
|
||||
#### It delegates to FilterTable::Table#where
|
||||
|
||||
|
@ -331,7 +336,7 @@ Lines 108-113 loop over the provided Hash `conditions`. If the requested field i
|
|||
|
||||
Line 118-135 begins work if a filtration block has been provided. At this point, `filtered_raw_data` has been initialized with the raw data, and (if method params were provided) has also been filtered down.
|
||||
|
||||
Line 120 filters the rows of the raw data using an interesting approach. Each row is inflated to a Struct using `create_eval_context_for_row` (see line 295). Then the provided block is `instance_eval`'d against the Struct. Because the Struct was defined with attributes (that is, accessor methods) for each declared field name (from FilterTable::Factory#register_custom_property), you can use field names in the block, and each row-as-struct will be able to respond. If the field happened to be lazy, we'll call our custom getter from lines 304-327.
|
||||
Line 120 filters the rows of the raw data using an interesting approach. Each row is inflated to a Struct using `create_eval_context_for_row` (see line 297). Then the provided block is `instance_eval`'d against the Struct. Because the Struct was defined with attributes (that is, accessor methods) for each declared field name (from FilterTable::Factory#register_custom_property), you can use field names in the block, and each row-as-struct will be able to respond. If the field happened to be lazy, we'll call our custom getter from lines 306-329.
|
||||
|
||||
_That just explained a major spooky side-effect for me._
|
||||
|
||||
|
|
|
@ -35,15 +35,11 @@ class Thing < Inspec.resource(1)
|
|||
|
||||
# FilterTable setup
|
||||
filter_table_config = FilterTable.create
|
||||
filter_table_config.register_filter_method(:where)
|
||||
filter_table_config.register_filter_method(:entries)
|
||||
filter_table_config.register_custom_matcher(:exist?) { |filter_table| !filter_table.entries.empty? }
|
||||
filter_table_config.register_custom_property(:count) { |filter_table| filter_table.entries.count }
|
||||
filter_table_config.register_column(:thing_ids, field: :thing_id)
|
||||
filter_table_config.register_column(:colors, field: :color, style: :simple)
|
||||
filter_table_config.install_filter_methods_on_resource(self, :fetch_data)
|
||||
|
||||
def fetch_data
|
||||
|
||||
def fetch_data
|
||||
# This method should return an array of hashes - the raw data. We'll hardcode it here.
|
||||
[
|
||||
{ thing_id: 1, color: :red },
|
||||
|
@ -61,9 +57,10 @@ end
|
|||
|
||||
Note that all of the methods on `filter_table_config` support chaining, so you will sometimes see it as:
|
||||
```ruby
|
||||
filter_table_config.register_filter_method(:where)
|
||||
.register_filter_method(:entries)
|
||||
.register_custom_matcher(:exist?) { |filter_table| !filter_table.entries.empty? }
|
||||
filter_table_config = FilterTable.create
|
||||
.register_column(:thing_ids, field: :thing_id)
|
||||
.register_column(:colors, field: :color, style: :simple)
|
||||
.install_filter_methods_on_resource(self, :fetch_data)
|
||||
```
|
||||
etc.
|
||||
|
||||
|
@ -71,6 +68,10 @@ etc.
|
|||
|
||||
With a (fairly standard) implementation like that above, what behavior do you get out of the box?
|
||||
|
||||
### Some things are defined for you
|
||||
|
||||
In the past, you needed to request certain methods be installed. These are now installed automatically: `where`, `entries`, `raw_data`, `count`, and `exist?`. You only have to declare your columns unique to your resource, and then attach the data fetcher.
|
||||
|
||||
### Your class is still just a Resource class
|
||||
|
||||
Nothing special immediately happens to your class or instances of it. The data fetcher is not called yet.
|
||||
|
|
|
@ -30,9 +30,42 @@ As this is the initial release of `aws_iam_groups`, its limited functionality pr
|
|||
|
||||
<br>
|
||||
|
||||
## Filter Criteria
|
||||
|
||||
### group_name
|
||||
|
||||
Filters the IAM groups by their group name, a string. If you know the exact group name, use `aws_iam_group` (singular) instead. This criteria may be used when you know a pattern of the name.
|
||||
|
||||
# Use a regex to find groups ending with 'Admins'
|
||||
describe aws_iam_groups.where(group_name: /Admins$/) do
|
||||
its('group_names') { should include 'FriendlyAdmins' }
|
||||
its('group_names') { shoud_not include 'ShunnedAdmins' }
|
||||
end
|
||||
|
||||
## Properties
|
||||
|
||||
### group_names
|
||||
|
||||
An Array of Strings, reflecting the IAM group names matched by the filter. If no groups matched, this will be empty. You can also use this with `aws_iam_group` to enumerate groups.
|
||||
|
||||
# Check for friendly people
|
||||
describe aws_iam_groups.where(group_name: /Admins$/) do
|
||||
its('group_names') { should include 'FriendlyAdmins' }
|
||||
its('group_names') { should include 'KindAdmins' }
|
||||
end
|
||||
|
||||
# Use to loop and fetch groups individually for auditing in detail
|
||||
# Without a `where`, this fetches all groups
|
||||
aws_iam_groups.group_names.each do |group_names|
|
||||
# A roundabout way of saying "bob should not be in any groups"
|
||||
describe aws_iam_group(group_name) do
|
||||
its('users') { should_not include 'bob' }
|
||||
end
|
||||
end
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers, please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
|
||||
This resource has no resource-specific matchers. For a full list of available matchers, please visit our [Universal Matchers page](https://www.inspec.io/docs/reference/matchers/).
|
||||
|
||||
### exists
|
||||
|
||||
|
|
|
@ -19,8 +19,7 @@ class AwsIamGroups < Inspec.resource(1)
|
|||
|
||||
# Underlying FilterTable implementation.
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
filter.add(:group_names, field: :group_name)
|
||||
filter.connect(self, :table)
|
||||
|
||||
def to_s
|
||||
|
|
|
@ -258,22 +258,24 @@ module FilterTable
|
|||
CustomPropertyType = Struct.new(:field_name, :block, :opts)
|
||||
|
||||
def initialize
|
||||
@filter_methods = []
|
||||
@filter_methods = [:where, :entries, :raw_data]
|
||||
@custom_properties = {}
|
||||
register_custom_matcher(:exist?) { |table| !table.raw_data.empty? }
|
||||
register_custom_property(:count) { |table| table.raw_data.count }
|
||||
|
||||
@resource = nil # TODO: this variable is never initialized
|
||||
end
|
||||
|
||||
def install_filter_methods_on_resource(resource_class, raw_data_fetcher_method_name) # rubocop: disable Metrics/AbcSize, Metrics/MethodLength
|
||||
struct_fields = @custom_properties.values.map(&:field_name)
|
||||
|
||||
# A context in which you can access the fields as accessors
|
||||
row_eval_context_type = Struct.new(*struct_fields.map(&:to_sym)) do
|
||||
non_block_struct_fields = @custom_properties.values.reject(&:block).map(&:field_name)
|
||||
row_eval_context_type = Struct.new(*non_block_struct_fields.map(&:to_sym)) do
|
||||
attr_accessor :criteria_string
|
||||
attr_accessor :filter_table
|
||||
def to_s
|
||||
@criteria_string || super
|
||||
end
|
||||
end unless struct_fields.empty?
|
||||
end unless non_block_struct_fields.empty?
|
||||
|
||||
properties_to_define = @custom_properties.map do |method_name, custom_property_structure|
|
||||
{ method_name: method_name, method_body: create_custom_property_body(custom_property_structure) }
|
||||
|
@ -294,7 +296,7 @@ module FilterTable
|
|||
# Install a method that can wrap all the fields into a context with accessors
|
||||
define_method :create_eval_context_for_row do |row_as_hash, criteria_string = ''|
|
||||
return row_eval_context_type.new if row_as_hash.nil?
|
||||
context = row_eval_context_type.new(*struct_fields.map { |field| row_as_hash[field] })
|
||||
context = row_eval_context_type.new(*non_block_struct_fields.map { |field| row_as_hash[field] })
|
||||
context.criteria_string = criteria_string
|
||||
context.filter_table = self
|
||||
context
|
||||
|
@ -355,7 +357,11 @@ module FilterTable
|
|||
# TODO: @resource is never initialized
|
||||
throw RuntimeError, "Called filter.add_accessor for resource #{@resource} with method name nil!"
|
||||
end
|
||||
@filter_methods.push(method_name.to_sym)
|
||||
if @filter_methods.include? method_name.to_sym
|
||||
# TODO: issue deprecation warning?
|
||||
else
|
||||
@filter_methods.push(method_name.to_sym)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
|
@ -367,8 +373,12 @@ module FilterTable
|
|||
throw RuntimeError, "Called filter.add for resource #{@resource} with method name nil!"
|
||||
end
|
||||
|
||||
@custom_properties[property_name.to_sym] =
|
||||
CustomPropertyType.new(opts[:field] || property_name, property_implementation, opts)
|
||||
if @custom_properties.key?(property_name.to_sym)
|
||||
# TODO: issue deprecation warning?
|
||||
else
|
||||
@custom_properties[property_name.to_sym] =
|
||||
CustomPropertyType.new(opts[:field] || property_name, property_implementation, opts)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
|
|
|
@ -50,9 +50,39 @@ describe '2943 inspec exec for filter table profile, method mode for `where' do
|
|||
end
|
||||
end
|
||||
|
||||
describe '3103 default methods for filter table' do
|
||||
include FunctionalHelper
|
||||
|
||||
it 'positive tests should pass' do
|
||||
controls = [
|
||||
'3103_where_defined',
|
||||
'3103_entries_defined',
|
||||
'3103_raw_data_defined',
|
||||
'3103_exists_defined',
|
||||
'3103_count_defined',
|
||||
]
|
||||
|
||||
cmd = 'exec ' + File.join(profile_path, 'filter_table')
|
||||
cmd += ' --reporter json --no-create-lockfile'
|
||||
cmd += ' --controls ' + controls.join(' ')
|
||||
cmd = inspec(cmd)
|
||||
|
||||
# RSpec keeps issuing a deprecation count to stdout; I can't seem to disable it.
|
||||
output = cmd.stdout.split("\n").reject {|line| line =~ /deprecation/}.join("\n")
|
||||
data = JSON.parse(output)
|
||||
failed_controls = data['profiles'][0]['controls'].select { |ctl| ctl['results'][0]['status'] == 'failed' }
|
||||
control_hash = {}
|
||||
failed_controls.each do |ctl|
|
||||
control_hash[ctl['id']] = ctl['results'][0]['message']
|
||||
end
|
||||
control_hash.must_be_empty
|
||||
cmd.exit_status.must_equal 0
|
||||
end
|
||||
end
|
||||
|
||||
describe '2370 lazy_load for filter table' do
|
||||
include FunctionalHelper
|
||||
|
||||
|
||||
it 'positive tests should pass' do
|
||||
controls = [
|
||||
'2370_where_block',
|
||||
|
@ -109,7 +139,7 @@ end
|
|||
|
||||
describe '2929 exceptions in block-mode where' do
|
||||
include FunctionalHelper
|
||||
|
||||
|
||||
it 'positive tests should pass' do
|
||||
controls = [
|
||||
'2929_exception_in_where',
|
||||
|
@ -128,3 +158,30 @@ describe '2929 exceptions in block-mode where' do
|
|||
cmd.exit_status.must_equal 0
|
||||
end
|
||||
end
|
||||
|
||||
describe '3110 do not expose block-valued properties in raw data' do
|
||||
include FunctionalHelper
|
||||
|
||||
it 'positive tests should pass' do
|
||||
controls = [
|
||||
'3110_entries_defined',
|
||||
'3110_raw_data_defined',
|
||||
]
|
||||
|
||||
cmd = 'exec ' + File.join(profile_path, 'filter_table')
|
||||
cmd += ' --reporter json --no-create-lockfile'
|
||||
cmd += ' --controls ' + controls.join(' ')
|
||||
cmd = inspec(cmd)
|
||||
|
||||
# RSpec keeps issuing a deprecation count to stdout; I can't seem to disable it.
|
||||
output = cmd.stdout.split("\n").reject {|line| line =~ /deprecation/}.join("\n")
|
||||
data = JSON.parse(output)
|
||||
failed_controls = data['profiles'][0]['controls'].select { |ctl| ctl['results'][0]['status'] == 'failed' }
|
||||
control_hash = {}
|
||||
failed_controls.each do |ctl|
|
||||
control_hash[ctl['id']] = ctl['results'][0]['message']
|
||||
end
|
||||
control_hash.must_be_empty
|
||||
cmd.exit_status.must_equal 0
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,31 @@
|
|||
control "aws_iam_groups recall" do
|
||||
describe aws_iam_groups do
|
||||
it { should exist }
|
||||
end
|
||||
fixtures = {}
|
||||
[
|
||||
'iam_group_administrators',
|
||||
].each do |fixture_name|
|
||||
fixtures[fixture_name] = attribute(
|
||||
fixture_name,
|
||||
default: "default.#{fixture_name}",
|
||||
description: 'See ../build/iam.tf',
|
||||
)
|
||||
end
|
||||
|
||||
control "aws_iam_groups search" do
|
||||
describe aws_iam_groups do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
describe aws_iam_groups.where(group_name: fixtures['iam_group_administrators']) do
|
||||
it { should exist }
|
||||
its('count') { should cmp 1 }
|
||||
end
|
||||
|
||||
describe aws_iam_groups.where(groups_name: /fakegroup/) do
|
||||
it { should_not exist }
|
||||
end
|
||||
end
|
||||
|
||||
control "aws_iam_groups properties test" do
|
||||
describe aws_iam_groups do
|
||||
its('group_names') { should include fixtures['iam_group_administrators'] }
|
||||
end
|
||||
end
|
104
test/unit/mock/profiles/filter_table/controls/default_methods.rb
Normal file
104
test/unit/mock/profiles/filter_table/controls/default_methods.rb
Normal file
|
@ -0,0 +1,104 @@
|
|||
title 'Verify default methods are defined - issue 3103'
|
||||
|
||||
fresh_data = ->() do
|
||||
[
|
||||
{ row_id: 0, color: :red }.dup,
|
||||
{ row_id: 1, color: :blue }.dup,
|
||||
{ row_id: 2, color: :green }.dup,
|
||||
]
|
||||
end
|
||||
|
||||
control '3103_where_defined' do
|
||||
desc 'The where filter method should be defined on both the resource and table'
|
||||
describe default_methods_demo(fresh_data.call) do
|
||||
it { should respond_to :where }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).where(color: :red) do
|
||||
it { should respond_to :where }
|
||||
it { should be_a_kind_of FilterTable::Table }
|
||||
end
|
||||
end
|
||||
|
||||
control '3103_entries_defined' do
|
||||
desc 'The entries method should be defined on both the resource and table'
|
||||
describe default_methods_demo(fresh_data.call) do
|
||||
it { should respond_to :entries }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).entries do
|
||||
it { should be_a_kind_of Array }
|
||||
its('first') { should be_a_kind_of Struct }
|
||||
its('count') { should eq 3 }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).where(color: :red).entries do
|
||||
it { should be_a_kind_of Array }
|
||||
its('first') { should be_a_kind_of Struct }
|
||||
its('count') { should eq 1 }
|
||||
end
|
||||
end
|
||||
|
||||
control '3103_raw_data_defined' do
|
||||
desc 'The raw_data method should be defined on both the resource and table'
|
||||
describe default_methods_demo(fresh_data.call) do
|
||||
it { should respond_to :raw_data }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).raw_data do
|
||||
it { should be_a_kind_of Array }
|
||||
its('first') { should be_a_kind_of Hash }
|
||||
its('count') { should eq 3 }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).where(color: :red).raw_data do
|
||||
it { should be_a_kind_of Array }
|
||||
its('first') { should be_a_kind_of Hash }
|
||||
its('count') { should eq 1 }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
control '3103_exist_defined' do
|
||||
desc 'The exist method should be defined on both the resource and table'
|
||||
describe default_methods_demo(fresh_data.call) do
|
||||
it { should respond_to :exist? }
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).exist? do
|
||||
it { should be_a_kind_of TrueClass }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).where(color: :red) do
|
||||
it { should respond_to :exist? }
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).where(color: :red).exist? do
|
||||
it { should be_a_kind_of TrueClass }
|
||||
end
|
||||
end
|
||||
|
||||
control '3103_count_defined' do
|
||||
desc 'The count method should be defined on both the resource and table'
|
||||
describe default_methods_demo(fresh_data.call) do
|
||||
it { should respond_to :count }
|
||||
its('count') { should eq 3 }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).count do
|
||||
it { should be_a_kind_of Integer }
|
||||
it { should eq 3 }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).where(color: :red) do
|
||||
it { should respond_to :count }
|
||||
its('count') { should eq 1 }
|
||||
end
|
||||
|
||||
describe default_methods_demo(fresh_data.call).where(color: :red).count do
|
||||
it { should be_a_kind_of Integer }
|
||||
it { should eq 1 }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
title 'Verify no internal properties are exposed in raw data - issue 3110'
|
||||
|
||||
fresh_data = ->() do
|
||||
[
|
||||
{ row_id: 0, color: :red }.dup,
|
||||
{ row_id: 1, color: :blue }.dup,
|
||||
{ row_id: 2, color: :green }.dup,
|
||||
]
|
||||
end
|
||||
|
||||
control '3110_entries' do
|
||||
desc 'The entries table should not have count or exist'
|
||||
describe default_methods_demo(fresh_data.call) do
|
||||
its('entries.first.to_h.keys') { should_not include :exist? }
|
||||
its('entries.first.to_h.keys') { should_not include :count }
|
||||
end
|
||||
end
|
||||
|
||||
control '3110_raw_data' do
|
||||
desc 'The raw_data table should not have count or exist'
|
||||
describe default_methods_demo(fresh_data.call) do
|
||||
its('raw_data.first.keys') { should_not include :exist? }
|
||||
its('raw_data.first.keys') { should_not include :count }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
class DefaultMethodsDemo < Inspec.resource(1)
|
||||
name 'default_methods_demo'
|
||||
|
||||
attr_reader :plain_data
|
||||
|
||||
def initialize(provided_data)
|
||||
@plain_data = provided_data
|
||||
end
|
||||
|
||||
filter_table_generator = FilterTable.create
|
||||
filter_table_generator.add(:ids, field: :id)
|
||||
filter_table_generator.connect(self, :plain_data)
|
||||
end
|
|
@ -32,7 +32,7 @@ class AwsIamGroupsRecallEmptyTest < Minitest::Test
|
|||
end
|
||||
|
||||
def test_search_miss_via_empty_groups
|
||||
refute AwsIamGroups.new.exists?
|
||||
refute AwsIamGroups.new.exist?
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -43,7 +43,7 @@ class AwsIamGroupsRecallBasicTest < Minitest::Test
|
|||
end
|
||||
|
||||
def test_search_hit_via_empty_query
|
||||
assert AwsIamGroups.new.exists?
|
||||
assert AwsIamGroups.new.exist?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -53,7 +53,6 @@ describe 'Inspec::Resources::Shadow' do
|
|||
user: 'root', password: 'x', last_change: '1',
|
||||
min_days: '2', max_days: '3', warn_days: nil,
|
||||
inactive_days: nil, expiry_date: nil, reserved: nil,
|
||||
content: nil, count: nil
|
||||
})
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue