inspec-core 2.3.10 → 2.3.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (216) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -13
  3. data/etc/plugin_filters.json +25 -0
  4. data/inspec-core.gemspec +1 -1
  5. data/lib/bundles/inspec-compliance/api.rb +3 -0
  6. data/lib/bundles/inspec-compliance/configuration.rb +3 -0
  7. data/lib/bundles/inspec-compliance/http.rb +3 -0
  8. data/lib/bundles/inspec-compliance/support.rb +3 -0
  9. data/lib/bundles/inspec-compliance/target.rb +3 -0
  10. data/lib/inspec/objects/attribute.rb +3 -0
  11. data/lib/inspec/plugin/v2.rb +3 -0
  12. data/lib/inspec/plugin/v2/filter.rb +62 -0
  13. data/lib/inspec/plugin/v2/installer.rb +21 -1
  14. data/lib/inspec/plugin/v2/loader.rb +4 -0
  15. data/lib/inspec/profile.rb +3 -1
  16. data/lib/inspec/version.rb +1 -1
  17. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +25 -3
  18. data/lib/plugins/inspec-plugin-manager-cli/test/functional/inspec-plugin_test.rb +65 -11
  19. data/lib/plugins/inspec-plugin-manager-cli/test/unit/cli_args_test.rb +5 -1
  20. data/lib/resources/package.rb +1 -1
  21. metadata +4 -197
  22. data/docs/.gitignore +0 -2
  23. data/docs/README.md +0 -41
  24. data/docs/dev/control-eval.md +0 -62
  25. data/docs/dev/filtertable-internals.md +0 -353
  26. data/docs/dev/filtertable-usage.md +0 -533
  27. data/docs/dev/integration-testing.md +0 -31
  28. data/docs/dev/plugins.md +0 -323
  29. data/docs/dsl_inspec.md +0 -354
  30. data/docs/dsl_resource.md +0 -100
  31. data/docs/glossary.md +0 -381
  32. data/docs/habitat.md +0 -193
  33. data/docs/inspec_and_friends.md +0 -114
  34. data/docs/matchers.md +0 -161
  35. data/docs/migration.md +0 -293
  36. data/docs/platforms.md +0 -119
  37. data/docs/plugin_kitchen_inspec.md +0 -60
  38. data/docs/plugins.md +0 -57
  39. data/docs/profiles.md +0 -576
  40. data/docs/reporters.md +0 -170
  41. data/docs/resources/aide_conf.md.erb +0 -86
  42. data/docs/resources/apache.md.erb +0 -77
  43. data/docs/resources/apache_conf.md.erb +0 -78
  44. data/docs/resources/apt.md.erb +0 -81
  45. data/docs/resources/audit_policy.md.erb +0 -57
  46. data/docs/resources/auditd.md.erb +0 -89
  47. data/docs/resources/auditd_conf.md.erb +0 -78
  48. data/docs/resources/bash.md.erb +0 -85
  49. data/docs/resources/bond.md.erb +0 -100
  50. data/docs/resources/bridge.md.erb +0 -67
  51. data/docs/resources/bsd_service.md.erb +0 -77
  52. data/docs/resources/chocolatey_package.md.erb +0 -68
  53. data/docs/resources/command.md.erb +0 -176
  54. data/docs/resources/cpan.md.erb +0 -89
  55. data/docs/resources/cran.md.erb +0 -74
  56. data/docs/resources/crontab.md.erb +0 -103
  57. data/docs/resources/csv.md.erb +0 -64
  58. data/docs/resources/dh_params.md.erb +0 -221
  59. data/docs/resources/directory.md.erb +0 -40
  60. data/docs/resources/docker.md.erb +0 -240
  61. data/docs/resources/docker_container.md.erb +0 -113
  62. data/docs/resources/docker_image.md.erb +0 -104
  63. data/docs/resources/docker_plugin.md.erb +0 -80
  64. data/docs/resources/docker_service.md.erb +0 -124
  65. data/docs/resources/elasticsearch.md.erb +0 -252
  66. data/docs/resources/etc_fstab.md.erb +0 -135
  67. data/docs/resources/etc_group.md.erb +0 -85
  68. data/docs/resources/etc_hosts.md.erb +0 -88
  69. data/docs/resources/etc_hosts_allow.md.erb +0 -84
  70. data/docs/resources/etc_hosts_deny.md.erb +0 -84
  71. data/docs/resources/file.md.erb +0 -543
  72. data/docs/resources/filesystem.md.erb +0 -51
  73. data/docs/resources/firewalld.md.erb +0 -117
  74. data/docs/resources/gem.md.erb +0 -108
  75. data/docs/resources/group.md.erb +0 -71
  76. data/docs/resources/grub_conf.md.erb +0 -111
  77. data/docs/resources/host.md.erb +0 -96
  78. data/docs/resources/http.md.erb +0 -207
  79. data/docs/resources/iis_app.md.erb +0 -132
  80. data/docs/resources/iis_site.md.erb +0 -145
  81. data/docs/resources/inetd_conf.md.erb +0 -104
  82. data/docs/resources/ini.md.erb +0 -86
  83. data/docs/resources/interface.md.erb +0 -68
  84. data/docs/resources/iptables.md.erb +0 -74
  85. data/docs/resources/json.md.erb +0 -73
  86. data/docs/resources/kernel_module.md.erb +0 -130
  87. data/docs/resources/kernel_parameter.md.erb +0 -63
  88. data/docs/resources/key_rsa.md.erb +0 -95
  89. data/docs/resources/launchd_service.md.erb +0 -67
  90. data/docs/resources/limits_conf.md.erb +0 -85
  91. data/docs/resources/login_defs.md.erb +0 -81
  92. data/docs/resources/mount.md.erb +0 -79
  93. data/docs/resources/mssql_session.md.erb +0 -78
  94. data/docs/resources/mysql_conf.md.erb +0 -109
  95. data/docs/resources/mysql_session.md.erb +0 -84
  96. data/docs/resources/nginx.md.erb +0 -89
  97. data/docs/resources/nginx_conf.md.erb +0 -148
  98. data/docs/resources/npm.md.erb +0 -78
  99. data/docs/resources/ntp_conf.md.erb +0 -70
  100. data/docs/resources/oneget.md.erb +0 -63
  101. data/docs/resources/oracledb_session.md.erb +0 -103
  102. data/docs/resources/os.md.erb +0 -153
  103. data/docs/resources/os_env.md.erb +0 -101
  104. data/docs/resources/package.md.erb +0 -130
  105. data/docs/resources/packages.md.erb +0 -77
  106. data/docs/resources/parse_config.md.erb +0 -113
  107. data/docs/resources/parse_config_file.md.erb +0 -148
  108. data/docs/resources/passwd.md.erb +0 -151
  109. data/docs/resources/pip.md.erb +0 -77
  110. data/docs/resources/port.md.erb +0 -147
  111. data/docs/resources/postgres_conf.md.erb +0 -89
  112. data/docs/resources/postgres_hba_conf.md.erb +0 -103
  113. data/docs/resources/postgres_ident_conf.md.erb +0 -86
  114. data/docs/resources/postgres_session.md.erb +0 -79
  115. data/docs/resources/powershell.md.erb +0 -112
  116. data/docs/resources/processes.md.erb +0 -119
  117. data/docs/resources/rabbitmq_config.md.erb +0 -51
  118. data/docs/resources/registry_key.md.erb +0 -197
  119. data/docs/resources/runit_service.md.erb +0 -67
  120. data/docs/resources/security_policy.md.erb +0 -57
  121. data/docs/resources/service.md.erb +0 -131
  122. data/docs/resources/shadow.md.erb +0 -267
  123. data/docs/resources/ssh_config.md.erb +0 -83
  124. data/docs/resources/sshd_config.md.erb +0 -93
  125. data/docs/resources/ssl.md.erb +0 -129
  126. data/docs/resources/sys_info.md.erb +0 -52
  127. data/docs/resources/systemd_service.md.erb +0 -67
  128. data/docs/resources/sysv_service.md.erb +0 -67
  129. data/docs/resources/upstart_service.md.erb +0 -67
  130. data/docs/resources/user.md.erb +0 -150
  131. data/docs/resources/users.md.erb +0 -137
  132. data/docs/resources/vbscript.md.erb +0 -65
  133. data/docs/resources/virtualization.md.erb +0 -67
  134. data/docs/resources/windows_feature.md.erb +0 -69
  135. data/docs/resources/windows_hotfix.md.erb +0 -63
  136. data/docs/resources/windows_task.md.erb +0 -95
  137. data/docs/resources/wmi.md.erb +0 -91
  138. data/docs/resources/x509_certificate.md.erb +0 -161
  139. data/docs/resources/xinetd_conf.md.erb +0 -166
  140. data/docs/resources/xml.md.erb +0 -95
  141. data/docs/resources/yaml.md.erb +0 -79
  142. data/docs/resources/yum.md.erb +0 -108
  143. data/docs/resources/zfs_dataset.md.erb +0 -63
  144. data/docs/resources/zfs_pool.md.erb +0 -57
  145. data/docs/shared/matcher_be.md.erb +0 -1
  146. data/docs/shared/matcher_cmp.md.erb +0 -43
  147. data/docs/shared/matcher_eq.md.erb +0 -3
  148. data/docs/shared/matcher_include.md.erb +0 -1
  149. data/docs/shared/matcher_match.md.erb +0 -1
  150. data/docs/shell.md +0 -217
  151. data/docs/style.md +0 -178
  152. data/examples/README.md +0 -8
  153. data/examples/custom-resource/README.md +0 -3
  154. data/examples/custom-resource/controls/example.rb +0 -7
  155. data/examples/custom-resource/inspec.yml +0 -8
  156. data/examples/custom-resource/libraries/batsignal.rb +0 -20
  157. data/examples/custom-resource/libraries/gordon.rb +0 -21
  158. data/examples/inheritance/README.md +0 -65
  159. data/examples/inheritance/controls/example.rb +0 -14
  160. data/examples/inheritance/inspec.yml +0 -16
  161. data/examples/kitchen-ansible/.kitchen.yml +0 -25
  162. data/examples/kitchen-ansible/Gemfile +0 -19
  163. data/examples/kitchen-ansible/README.md +0 -53
  164. data/examples/kitchen-ansible/files/nginx.repo +0 -6
  165. data/examples/kitchen-ansible/tasks/main.yml +0 -16
  166. data/examples/kitchen-ansible/test/integration/default/default.yml +0 -5
  167. data/examples/kitchen-ansible/test/integration/default/web_spec.rb +0 -28
  168. data/examples/kitchen-chef/.kitchen.yml +0 -20
  169. data/examples/kitchen-chef/Berksfile +0 -3
  170. data/examples/kitchen-chef/Gemfile +0 -19
  171. data/examples/kitchen-chef/README.md +0 -27
  172. data/examples/kitchen-chef/metadata.rb +0 -7
  173. data/examples/kitchen-chef/recipes/default.rb +0 -6
  174. data/examples/kitchen-chef/recipes/nginx.rb +0 -30
  175. data/examples/kitchen-chef/test/integration/default/web_spec.rb +0 -28
  176. data/examples/kitchen-puppet/.kitchen.yml +0 -23
  177. data/examples/kitchen-puppet/Gemfile +0 -20
  178. data/examples/kitchen-puppet/Puppetfile +0 -25
  179. data/examples/kitchen-puppet/README.md +0 -53
  180. data/examples/kitchen-puppet/manifests/site.pp +0 -33
  181. data/examples/kitchen-puppet/metadata.json +0 -11
  182. data/examples/kitchen-puppet/modules/.gitkeep +0 -0
  183. data/examples/kitchen-puppet/test/integration/default/web_spec.rb +0 -28
  184. data/examples/meta-profile/README.md +0 -37
  185. data/examples/meta-profile/controls/example.rb +0 -13
  186. data/examples/meta-profile/inspec.yml +0 -13
  187. data/examples/plugins/inspec-resource-lister/Gemfile +0 -12
  188. data/examples/plugins/inspec-resource-lister/LICENSE +0 -13
  189. data/examples/plugins/inspec-resource-lister/README.md +0 -62
  190. data/examples/plugins/inspec-resource-lister/Rakefile +0 -40
  191. data/examples/plugins/inspec-resource-lister/inspec-resource-lister.gemspec +0 -45
  192. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister.rb +0 -16
  193. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister/cli_command.rb +0 -70
  194. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister/plugin.rb +0 -55
  195. data/examples/plugins/inspec-resource-lister/lib/inspec-resource-lister/version.rb +0 -10
  196. data/examples/plugins/inspec-resource-lister/test/fixtures/README.md +0 -24
  197. data/examples/plugins/inspec-resource-lister/test/functional/README.md +0 -18
  198. data/examples/plugins/inspec-resource-lister/test/functional/inspec_resource_lister_test.rb +0 -110
  199. data/examples/plugins/inspec-resource-lister/test/helper.rb +0 -26
  200. data/examples/plugins/inspec-resource-lister/test/unit/README.md +0 -17
  201. data/examples/plugins/inspec-resource-lister/test/unit/cli_args_test.rb +0 -64
  202. data/examples/plugins/inspec-resource-lister/test/unit/plugin_def_test.rb +0 -51
  203. data/examples/profile-attribute.yml +0 -2
  204. data/examples/profile-attribute/README.md +0 -14
  205. data/examples/profile-attribute/controls/example.rb +0 -11
  206. data/examples/profile-attribute/inspec.yml +0 -8
  207. data/examples/profile-sensitive/README.md +0 -29
  208. data/examples/profile-sensitive/controls/sensitive-failures.rb +0 -9
  209. data/examples/profile-sensitive/controls/sensitive.rb +0 -9
  210. data/examples/profile-sensitive/inspec.yml +0 -8
  211. data/examples/profile/README.md +0 -48
  212. data/examples/profile/controls/example.rb +0 -24
  213. data/examples/profile/controls/gordon.rb +0 -36
  214. data/examples/profile/controls/meta.rb +0 -36
  215. data/examples/profile/inspec.yml +0 -11
  216. data/examples/profile/libraries/gordon_config.rb +0 -59
@@ -1,533 +0,0 @@
1
- # Using FilterTable to write a Resource
2
-
3
- ## When do I use FilterTable?
4
-
5
- FilterTable is intended to help you author "plural" resources.
6
-
7
- Plural resources examine platform objects in bulk. For example, sorting through which packages are installed on a system, or which virtual machines are on a cloud provider. You don't know the identifiers of the objects, but you may know some of their properties, and you may want to filter the objects based on those - for example, all processes running more than an hour, or all VMs on a particular subnet.
8
-
9
- Singular resources, in contrast, are designed to examine a particular platform object in detail, _when you know an identifier_. For example, you would use a singular resource to fetch a VM by its ID, then interrogate its networking configuration. Singular resources are able to provide richer properties and matchers than plural resources, because the semantics are clearer.
10
-
11
- If you can't tell if the resource you are authoring is singular or plural, STOP and consult with a team member. This is a fundamental design question, and while we have had some resources that "straddle the fence" in the past, they are very difficult to use and maintain.
12
-
13
- ### Should I use FilterTable to represent list properties?
14
-
15
- Suppose you have a person, and you want to represent that person's shoes. Should you use FilterTable for that?
16
-
17
- NO. FilterTable is intended to represent pluralities inherent to the resource itself, not a property of the resource. So, you would use FilterTable to represent _people_. To represent shoes, you could have a simple, dumb array-of-strings property on Person. Or, you could create a new resource, Shoe, or Shoes, which has a person_name or person_id property. Or expose a complex structure as a low-level property, and create mid-level properties/matchers that compute on the values internally (`shoe_fit?`, `has_shoes_for_occasion?('red_carpet')`)
18
-
19
- ### May I have multiple FilterTable installations on a class?
20
-
21
- In theory, yes - that would be used to implement different data fetching / caching strategies. It is a very advanced usage, and no core resources currently do this, as far as I know.
22
-
23
- ## Example Usage
24
-
25
- Suppose you are writing a resource, `things`. You want it to behave like any plural resource (we'll explore what that means in a moment). That is the basic expected behavior of any plural resource.
26
-
27
- ### How do I declare my interaction with FilterTable?
28
-
29
- ```ruby
30
-
31
- require 'utils/filter'
32
-
33
- class Thing < Inspec.resource(1)
34
- #... other Resource DSL work goes here ...
35
-
36
- # FilterTable setup
37
- filter_table_config = FilterTable.create
38
- filter_table_config.register_column(:thing_ids, field: :thing_id)
39
- filter_table_config.register_column(:colors, field: :color, style: :simple)
40
- filter_table_config.install_filter_methods_on_resource(self, :fetch_data)
41
-
42
- def fetch_data
43
- # This method should return an array of hashes - the raw data. We'll hardcode it here.
44
- [
45
- { thing_id: 1, color: :red },
46
- { thing_id: 2, color: :blue, tackiness: 'very' },
47
- { thing_id: 3, color: :red },
48
- ]
49
- end
50
-
51
- def some_other_property
52
- # We'll examine this later
53
- end
54
-
55
- end
56
- ```
57
-
58
- Note that all of the methods on `filter_table_config` support chaining, so you will sometimes see it as:
59
- ```ruby
60
- filter_table_config = FilterTable.create
61
- .register_column(:thing_ids, field: :thing_id)
62
- .register_column(:colors, field: :color, style: :simple)
63
- .install_filter_methods_on_resource(self, :fetch_data)
64
- ```
65
- etc.
66
-
67
- ## Standard behavior
68
-
69
- With a (fairly standard) implementation like that above, what behavior do you get out of the box?
70
-
71
- ### Some things are defined for you
72
-
73
- 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.
74
-
75
- ### Your class is still just a Resource class
76
-
77
- Nothing special immediately happens to your class or instances of it. The data fetcher is not called yet.
78
-
79
- ### Instances of your class can create a specialized FilterTable::Table instance
80
-
81
- When most of the following methods are called, it may trigger the instantiation of a FilterTable::Table anonymous subclass. That instance will have called the raw data fetcher, and will wrap the raw data inside it. Many of the following methods return the Table instance.
82
-
83
- ### A `where` method you can call with nil to get the Table
84
-
85
- The resource class gains a method, `where`. If called with a single `nil` param or no params, it will call the data fetcher method, wrap it up, and return the Table instance. Calling `where` in other modes will do the same thing, but will filter the data.
86
-
87
- ```ruby
88
- describe things.where(nil)
89
- it { should exist }
90
- its('count') { should cmp 3 }
91
- end
92
-
93
- # This works, too, but for different internal reasons
94
- describe things.where
95
- it { should exist }
96
- its('count') { should cmp 3 }
97
- end
98
- ```
99
-
100
- ### A `where` method you can call with hash params, with loose matching
101
-
102
- If you call `where` as a method with no block and passing hash params, with keys you know are in the raw data, it will fetch the raw data, then filter row-wise and return the resulting Table.
103
-
104
- Multiple criteria are joined with a logical AND.
105
-
106
- The filtering is fancy, not just straight equality.
107
-
108
- ```ruby
109
- describe things.where(color: :red) do
110
- its('count') { should cmp 2 }
111
- end
112
-
113
- # Regexes
114
- describe things.where(color: /^re/) do
115
- its('count') { should cmp 2 }
116
- end
117
-
118
- # It eventually falls out to === comparison
119
- # Here, range membership 1..2
120
- describe things.where(thing_id: (1..2)) do
121
- its('count') { should cmp 2 }
122
- end
123
-
124
- # Things that don't exist are silently ignored, but do not match
125
- # See https://github.com/chef/inspec/issues/2943
126
- describe things.where(none_such: :nope) do
127
- its('count') { should cmp 0 }
128
- end
129
-
130
- # irregular rows are supported
131
- # Only one row has the :tackiness key, with value 'very'.
132
- describe things.where(tackiness: 'very') do
133
- its('count') { should cmp 1 }
134
- end
135
-
136
- ```
137
-
138
- ### A `where` method you can call with a block, referencing some fields
139
-
140
- You can also call the `where` method with a block. The block is executed row-wise. If it returns truthy, the row is included in the results. register_custom_propertyitionally, within the block each field declared with the `register_custom_property` configuration method is available as a data accessor.
141
-
142
- ```ruby
143
-
144
- # You can have any logic you want in the block
145
- describe things.where { true } do
146
- its('count') { should cmp 3 }
147
- end
148
-
149
- # You can access any fields you declared using `register_column`
150
- describe things.where { thing_id > 2 } do
151
- its('count') { should cmp 1 }
152
- end
153
- ```
154
-
155
- ### You can chain off of `where` or any other Table without re-fetching raw data
156
-
157
- The first time `where` is called, the data fetcher method is called. `where` performs filtration on the raw data table. It then constructs a new FilterTable::Table, directly passing in the filtered raw data; this is then the return value from `where`.
158
-
159
- ```ruby
160
- # This only calls fetch_data once
161
- describe things.where(color: :red).where { thing_id > 2 } do
162
- its('count') { should cmp 1 }
163
- end
164
- ```
165
-
166
- Some other methods return a Table object, and they may be chained without a re-fetch as well.
167
-
168
- ### An `entries` method that will return an array of Structs
169
-
170
- The other `register_filter_method` call enables a pre-defined method, `entries`. `entries` is much simpler than `where` - in fact, its behavior is unrelated. It returns an encapsulated version of the raw data - a plain array, containing Structs as row-entries. Each struct has an attribute for each time you called `register_column`.
171
-
172
- Overall, in my opinion, `entries` is less useful than `params` (which returns the raw data). Wrapping in Structs does not seem to add much benefit.
173
-
174
- Importantly, note that the return value of `entries` is not the resource, nor the Table - in other words, you cannot chain it. However, you can call `entries` on any Table.
175
-
176
- If you call `entries` without chaining it after `where`, calling entries will trigger the call to the data fetching method.
177
-
178
- ```ruby
179
-
180
- # Access the entries array
181
- describe things.entries do
182
- # This is Array#count, not the resource's `count` method
183
- its('count') { should cmp 3}
184
- end
185
-
186
- # Access the entries array after chaining off of where
187
- describe things.where(color: :red).entries do
188
- # This is Array#count, not the resource's or table's `count` method
189
- its('count') { should cmp 2}
190
- end
191
-
192
- # You can access the struct elements as a method, as a hash keyed on symbol, or as a hash keyed on string
193
- describe things.entries.first.color do
194
- it { should cmp :red }
195
- end
196
- describe things.entries.first[:color] do
197
- it { should cmp :red }
198
- end
199
- describe things.entries.first['color'] do
200
- it { should cmp :red }
201
- end
202
- ```
203
-
204
- ### You get an `exist?` matcher defined on the resource and the table
205
-
206
- This `register_custom_matcher` call:
207
- ```ruby
208
- filter_table_config.register_custom_matcher(:exist?) { |filter_table| !filter_table.entries.empty? }
209
- ```
210
-
211
- causes a new method to be defined on both the resource class and the Table class. The body of the method is taken from the block that is provided. When the method it called, it will receive the FilterTable::Table instance as its first parameter. (It may also accept a second param, but that doesn't make sense for this method - see thing_ids).
212
-
213
- As when you are implementing matchers on a singular resource, the only thing that distinguishes this as a matcher is the fact that it ends in `?`.
214
-
215
- ```ruby
216
- # Bare call on the matcher (called as a method on the resource)
217
- describe things do
218
- it { should exist }
219
- end
220
-
221
- # Chained on where (called as a method on the Table)
222
- describe things.where(color: :red) do
223
- it { should exist }
224
- end
225
- ```
226
-
227
- ### You get an `count` property defined on the resource and the table
228
-
229
- This `register_custom_property` call:
230
- ```ruby
231
- filter_table_config.register_custom_property(:count) { |filter_table| filter_table.entries.count }
232
- ```
233
-
234
- causes a new method to be defined on both the resource class and the Table class. As with `exists?`, the body is taken from the block.
235
-
236
- ```ruby
237
- # Bare call on the property (called as a method on the resource)
238
- describe things do
239
- its('count') { should cmp 3 }
240
- end
241
-
242
- # Chained on where (called as a method on the Table)
243
- describe things.where(color: :red) do
244
- its('count') { should cmp 2 }
245
- end
246
- ```
247
-
248
- ### A `thing_ids` method that will return an array of plain values when called without params
249
-
250
- This `register_column` call:
251
- ```ruby
252
- filter_table_config.register_column(:thing_ids, field: :thing_id)
253
- ```
254
-
255
- will cause a method to be defined on both the resource and the Table. Note that this `register_column` call does not provide a block; so FilterTable::Factory generates a method body. The `:field` option specifies which column to access in the raw data (that is, which hash key in the array-of-hashes).
256
-
257
- The implementation provided by Factory changes behavior based on calling pattern. If no params or block is provided, a simple array is returned, containing the column-wise values in the raw data.
258
-
259
- ```ruby
260
-
261
- # Use it to check for presence / absence of a member
262
- # This retains nice output formatting - we're testing on a Table associated with a Things resource
263
- describe things.where(color: :red) do
264
- its('thing_ids') { should include 3 }
265
- end
266
-
267
- # Equivalent but with poor formatting - we're testing an anonymous array
268
- describe things.where(color: :red).thing_ids do
269
- it { should include 3 }
270
- end
271
-
272
- # Use as a test-less enumerator
273
- things.where(color: :red).thing_ids.each do |thing_id|
274
- # Do something with thing_id, maybe
275
- # describe thing(thing_id) do ...
276
- end
277
-
278
- # Can be used without where - enumerates all Thing IDs with no filter
279
- things.thing_ids.each do |thing_id|
280
- # Do something with thing_id, maybe
281
- # describe thing(thing_id) do ...
282
- end
283
-
284
- ```
285
-
286
- ### A `colors` method that will return a flattened and uniq'd array of values
287
-
288
- This method behaves just like `thing_ids`, except that it returns the values of the `color` column. In addition, the `style: :simple` option causes it to flatten and uniq the array of values when called without args or a block.
289
-
290
- ```ruby
291
- # Three rows in the data: red, blue, red
292
- describe things.colors do
293
- its('count') { should cmp 2 }
294
- it { should include :red }
295
- it { should include :blue }
296
- end
297
-
298
- ```
299
-
300
- ### A `colors` method that can filter on a value and return a Table
301
-
302
- You also get this for `thing_ids`. This is unrelated to `style: :simple` for `colors`.
303
-
304
- People definitely use this in the wild. It reads badly to me; I think this is a legacy usage that we should consider deprecating. To me, this seems to imply that there is a sub-resource (here, colors) we are auditing. At least two core resouces (`xinetd_conf` and `users`) advocate this as their primary use.
305
-
306
- ```ruby
307
- # Filter on colors
308
- describe things.colors(:red) do
309
- its('count') { should cmp 2 }
310
- end
311
-
312
- # Same, but doesn't imply we're now operating on some 'color' resource
313
- describe things.where(color: :red) do
314
- its('count') { should cmp 2 }
315
- end
316
- ```
317
-
318
- ### A `colors` method that can filter on a block and return a Table
319
-
320
- You also get this for `thing_ids`. This is unrelated to `style: :simple` for `colors`.
321
-
322
- I haven't seen this used in the wild, but its existence gives me a headache.
323
-
324
- ```ruby
325
- # Example A, B, C, and D are semantically the same
326
-
327
- # A: Filter both on colors and the block
328
- describe things.colors(:red) { thing_id < 2 } do
329
- its('count') { should cmp 1 }
330
- its('thing_ids') { should include 1 }
331
- end
332
-
333
- # B use one where block
334
- describe things.where { color == :red && thing_id < 2 } do
335
- its('count') { should cmp 1 }
336
- its('thing_ids') { should include 1 }
337
- end
338
-
339
- # C use two where blocks
340
- describe things.where { color == :red }.where { thing_id < 2 } do
341
- its('count') { should cmp 1 }
342
- its('thing_ids') { should include 1 }
343
- end
344
-
345
- # D use a where param and a where block
346
- describe things.where(color: :red) { thing_id < 2 } do
347
- its('count') { should cmp 1 }
348
- its('thing_ids') { should include 1 }
349
- end
350
-
351
- # This has nothing to do with colors at all, and may be broken - the lack of an arg to `colors` may make it never match
352
- describe things.colors { thing_id < 2 } do
353
- its('count') { should cmp 1 }
354
- end
355
- ```
356
-
357
- ### You can call `params` or `raw_data` on any Table to get the raw data
358
-
359
- People _definitely_ use this out in the wild. Unlike `entries`, which wraps each row in a Struct and omits undeclared fields, `raw_data` simply returns the actual raw data array-of-hashes. It is not `dup`'d.
360
-
361
- ```ruby
362
- tacky_things = things.where(color: :blue).raw_data.select { |row| row[:tackiness] }
363
- tacky_things.map { |row| row[:thing_id] }.each do |thing_id|
364
- # Use to audit a singular Thing
365
- describe thing(thing_id) do
366
- it { should_not be_paisley }
367
- end
368
- end
369
- ```
370
-
371
- ### You can call `resource_instance` on any Table to get the resource instance
372
-
373
- You could use this to do something fairly complicated.
374
-
375
- ```ruby
376
- describe things.where do # Just getting a Table object
377
- its('resource_instance.some_method') { should cmp 'some_value' }
378
- end
379
- ```
380
-
381
- However, the resource instance won't know about the filtration, so I'm not sure what good this does. Chances are, someone is doing something horrid using this feature in the wild.
382
-
383
- ## Lazy Loading
384
-
385
- ### What is Lazy Loading
386
-
387
- In some cases, the raw data may require multiple actions to populate. For example, if you wanted a list of processes, and their open files, you might need to call 'ps' once, then 'lsof' one or more times. That would become slow, and so you would only want to do it if you knew it was going to be used.
388
-
389
- Lazy loaded columns are absent in the raw data, until they are accessed (either by method-where, block-where, or a list property). When they are accessed, a user-provided Lambda is called, which populates one or more columns. FilterTable remembers which lazy columns have been populated, and will not call the lambda again.
390
-
391
- ### Declaring a lazy field
392
-
393
- You declare a field to be lazy by providing an option, `lazy`, whose value is the lambda to be called.
394
-
395
- You can use the 'stabby lambda' syntax:
396
-
397
- ```ruby
398
- filter_table_config.register_column(
399
- :open_files,
400
- field: :files,
401
- lazy: ->() {|r,c,t| r[:files] = lookup_files_for_pid(r[:pid])},
402
- )
403
- ```
404
-
405
- You can also refer to a *class* method. You cannot use an instance method, because FilterTable binds to the resource class, not the resource instance.
406
-
407
- ```ruby
408
- def self.populate_lsof(row, criteria, table)
409
- row[:files] = ...
410
- end
411
- filter_table_config.register_column(
412
- :open_files,
413
- field: :files,
414
- lazy: method(:populate_lsof),
415
- )
416
- ```
417
-
418
- ### Arguments to the fetcher lambda
419
-
420
- The lambda will be provided three arguments:
421
-
422
- 1. `row`. This is a Hash, the current row of the raw_data. You will likely need to examine this to find an ID value or other field that will act as a search key for your fetch. You are expected to add one or more entries to this hash, as a result of your fetch.
423
- 2. `condition`. In some cases, a condition (desired value) is provided; the semantics of this are up to you.
424
- 3. `table`. A reference to the FilterTable. You can use this to access other context - including the entire raw data (`table.raw_data`) or the resource instance (`table.resource_instance`).
425
-
426
- ### Clobbering
427
-
428
- Lazy-loading will not clobber an existing value in raw data. For example:
429
-
430
- ```ruby
431
- # Your raw data table:
432
- [
433
- { id: 1 },
434
- { id: 2, color: :blue },
435
- { id: 3 },
436
- ]
437
-
438
- # On lazy load, set all rows to color red
439
- filter_table_config.register_column(
440
- :colors,
441
- field: :color,
442
- lazy: ->() { |r,c,t| r[:color] = :red },
443
- )
444
-
445
- # Trigger a fetch
446
- my_resource.colors => [:red, :blue, :red]
447
-
448
- # Raw data now:
449
- [
450
- { id: 1, color: :red },
451
- { id: 2, color: :blue },
452
- { id: 3, color: :red },
453
- ]
454
- ```
455
- Note that not only was the `:color` blue not overwritten, in fact the fetcher lambda was only called twice.
456
-
457
- ### Can I set multiple columns at once?
458
-
459
- Yes. If your fetching action provides you with data to populate multiple columns, you are free to set any columns you wish in the `row`.
460
-
461
- You can even have multiple lazy columns share an implementation; the first one to be called will populate all the columns that share that implementation, and if any of the others are later triggered, the no-clobber effect will kick in, and the fetcher will not be called again.
462
-
463
- ### Can I set multiple rows at once?
464
-
465
- Yes. Using `table.raw_data`, you could perform a column-at-once population. After the fetcher was called for the first row, all other rows would already be populated, so the fetcher would not be called again due to the no-clobber effect.
466
-
467
- ## Gotchas and Surprises
468
-
469
- ### Methods defined with `register_column` will change their return type based on their call pattern
470
-
471
- To me, calling things.thing_ids should always return the same type of value. But if you call it with args or a block, it not only runs a filter, but also changes its return type to Table.
472
-
473
- ```ruby
474
-
475
- # This is an Array of color values (symbols, here)
476
- things.colors
477
-
478
- # This is a FilterTable::Table and these are equivalent
479
- things.colors(:red)
480
- things.where(color: :red)
481
-
482
- # This is a FilterTable::Table and these are equivalent
483
- things.colors { color == :red } # I think there is a bug here which makes this never match
484
- things.where(color: :red)
485
-
486
- ```
487
-
488
- ### `entries` will not have fields present in the raw data
489
-
490
- `entries` will only know about the fields declared by `register_column` with `field:`. And...
491
-
492
- ### `entries` will have things that are not fields
493
-
494
- Each time you call `register_custom_property`, `register_custom_matcher` or `register_column` - even for things like `count` and `exists?` - that will add an attribute to the Struct that is used to represent a row. Those attributes will always be nil.
495
-
496
- ### `register_column` does not know about what fields are in the raw data
497
-
498
- This is because the raw data fetcher is not called until as late as possible. That's good - it might be expensive - but it also means we can't scan it for columns. There are ways around that.
499
-
500
- ### `where` param-mode and raw data access is sensitive to symbols vs strings
501
-
502
- ### You can't call resource methods on a Table directly
503
-
504
- ### You can't use a column name in a `where` block unless it was declared as a field using `register_column`
505
-
506
- ```ruby
507
- # This will give a NameError - :tackiness is in the raw
508
- # data hash but not declared using `register_custom_property`.
509
- describe things.where { tackiness == 'very' } do
510
- its('count') { should cmp 1 }
511
- end
512
- # NameError: undefined local variable or method `tackiness' for #<struct :exists?=nil, count=nil, id=nil>
513
-
514
- # But this works:
515
- describe things.where(tackiness: 'very') do
516
- its('count') { should cmp 1 }
517
- end
518
- ```
519
-
520
- ### The eval context of a where block is an anonymous Struct class
521
-
522
- You can't get to the resource or the table from there. (It's the Entry Struct type).
523
-
524
- ### You can in fact filter for `nil` values
525
-
526
- ### There is no obvious accessor for the Table instance
527
-
528
- You can in fact get the FilterTable::Table instance by calling `where` with no args. But that is not obvious.
529
-
530
- ### There is no way to get the FilterTable::Factory object used to configure the resource
531
-
532
- Especially while developing in inspec shell, it would be nice to be able to get at the FilterTable::Factory object, perhaps to add more accessors.
533
-