-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Advanced Class updates #275
Changes from 11 commits
86c3264
d30a5a2
3f508a3
b460fa8
957bd3d
23365dc
b5aa7c2
4d5056d
4326af8
e5f3bc9
7e11494
4b37611
efa4d53
c97112b
e7d2fc2
4ce7311
fbde7dd
3bbf5f7
d914fce
3543dbd
3729d83
7c02605
29b25b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -14,7 +14,7 @@ Now that you have learned about making and running InSpec profiles, let's dig de | |||||
|
||||||
As you saw in the [Beginner class](../beginner/README.md), when writing InSpec code, many core resources are available because they are included in the main InSpec code base. | ||||||
wdower marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
* You can [explore the core InSpec resources](https://www.inspec.io/docs/reference/resources/) to existing resources. | ||||||
* You can [explore the core InSpec resources](https://www.inspec.io/docs/reference/resources/) on Chef's documentation website. | ||||||
* You can also [examine the source code](https://github.com/inspec/inspec/tree/master/lib/inspec/resources) to see what's available. For example, you can see how `file` and other InSpec resources are implemented. | ||||||
|
||||||
### Local Resources | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can't reach with the auto suggestion, but like the other day, let's get rid of the pointless tree invocations and only show the results of that call |
||||||
|
@@ -30,30 +30,9 @@ Note that the `libraries` directory is not created by default within a profile w | |||||
|
||||||
Once you create and populate a custom resource Ruby file inside the `libraries` directory, it can be utilized inside your local profile just like the core resources. | ||||||
|
||||||
### 6.1. Resource Overview | ||||||
### Resource Structure | ||||||
|
||||||
Resources may be added to profiles in the libraries folder: | ||||||
```bash | ||||||
$ tree examples/profile | ||||||
examples/profile | ||||||
... | ||||||
├── libraries | ||||||
│ └── gordon_config.rb | ||||||
``` | ||||||
|
||||||
### 6.2. Resource Structure | ||||||
|
||||||
The smallest possible InSpec resource takes this form: | ||||||
|
||||||
```ruby | ||||||
class Tiny < Inspec.resource(1) | ||||||
name 'tiny' | ||||||
end | ||||||
``` | ||||||
|
||||||
This is easy to write, but not particularly useful for testing. | ||||||
|
||||||
Resources are written as a regular Ruby class, which inherits from the base `inspec.resource` class. The number (1) specifies the version this resource plugin targets. As Chef InSpec evolves, this interface may change and may require a higher version. | ||||||
Resources are written as regular Ruby classes, which inherit from the base `inspec.resource` class. The number (1) specifies the version of the parent class that this resource plugin uses. As Chef InSpec evolves, this interface may change and may require a higher version. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have not shown the reader an example resource yet so this explanation is basically useless. We need to follow the pattern that we used for introducing an inspec control here as opposed to an inspec profile one since the user needs to look at and then can understand the code.
wdower marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
In addition to the resource name, the following attributes can be configured: | ||||||
|
||||||
|
@@ -67,67 +46,99 @@ 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 following example shows a full resource using attributes and methods to provide simple access to a configuration file: | ||||||
### The `etc_hosts` example | ||||||
|
||||||
Let's look at a simple default resource to get an idea how these resources are used. We'll take a look at the [source code](https://github.com/inspec/inspec/blob/526b52657be571ba1573c12d666dc1f6330f2307/lib/inspec/resources/etc_hosts.rb) for the InSpec resource that models an operating system's hostfile, which is a simple file where we can map IP addresses (e.g. 198.162.8.1) to domain names (e.g. my-heimdall-deployment.my-domain.dev) without having to add a record to a DNS server somewhere. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
dev got taken by google :shakesfist: so we should probably find a better example ip address and domain name got rid of the dns server jank cause the only people who'll know what we're talking about don't need the explanation on the difference. getting rid of the jargon makes it easier for folks. |
||||||
|
||||||
The example shows a full resource using attributes and methods to return data about the modeled file: | ||||||
wdower marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
```ruby | ||||||
class GordonConfig < Inspec.resource(1) | ||||||
name 'gordon_config' | ||||||
|
||||||
# Restrict to only run on the below platforms (if none were given, all OS's supported) | ||||||
supports platform_family: 'fedora' | ||||||
supports platform: 'centos', release: '6.9' | ||||||
# Supports `*` for wildcard matcher in the release | ||||||
supports platform: 'centos', release: '7.*' | ||||||
|
||||||
desc ' | ||||||
Resource description ... | ||||||
' | ||||||
|
||||||
example ' | ||||||
describe gordon_config do | ||||||
its("signal") { should eq "on" } | ||||||
require "inspec/utils/parser" | ||||||
require "inspec/utils/file_reader" | ||||||
|
||||||
module Inspec::Resources | ||||||
class EtcHosts < Inspec.resource(1) | ||||||
name "etc_hosts" | ||||||
supports platform: "unix" | ||||||
supports platform: "windows" | ||||||
desc 'Use the etc_hosts InSpec audit resource to find an | ||||||
ip_address and its associated hosts' | ||||||
example <<~EXAMPLE | ||||||
describe etc_hosts.where { ip_address == '127.0.0.1' } do | ||||||
its('ip_address') { should cmp '127.0.0.1' } | ||||||
its('primary_name') { should cmp 'localhost' } | ||||||
its('all_host_names') { should eq [['localhost', 'localhost.localdomain', 'localhost4', 'localhost4.localdomain4']] } | ||||||
end | ||||||
EXAMPLE | ||||||
|
||||||
attr_reader :params | ||||||
|
||||||
include Inspec::Utils::CommentParser | ||||||
include FileReader | ||||||
|
||||||
DEFAULT_UNIX_PATH = "/etc/hosts".freeze | ||||||
DEFAULT_WINDOWS_PATH = 'C:\windows\system32\drivers\etc\hosts'.freeze | ||||||
|
||||||
def initialize(hosts_path = nil) | ||||||
content = read_file_content(hosts_path || default_hosts_file_path) | ||||||
|
||||||
@params = parse_conf(content.lines) | ||||||
end | ||||||
' | ||||||
|
||||||
# Load the configuration file on initialization | ||||||
def initialize(path = nil) | ||||||
@path = path || '/etc/gordon.conf' | ||||||
@params = SimpleConfig.new( read_content ) | ||||||
end | ||||||
FilterTable.create | ||||||
.register_column(:ip_address, field: "ip_address") | ||||||
.register_column(:primary_name, field: "primary_name") | ||||||
.register_column(:all_host_names, field: "all_host_names") | ||||||
.install_filter_methods_on_resource(self, :params) | ||||||
|
||||||
# Expose all parameters of the configuration file. | ||||||
def method_missing(name) | ||||||
@params[name] | ||||||
end | ||||||
def to_s | ||||||
"Hosts File" | ||||||
end | ||||||
|
||||||
private | ||||||
|
||||||
def default_hosts_file_path | ||||||
inspec.os.windows? ? DEFAULT_WINDOWS_PATH : DEFAULT_UNIX_PATH | ||||||
end | ||||||
|
||||||
def parse_conf(lines) | ||||||
lines.reject(&:empty?).reject(&comment?).map(&parse_data).map(&format_data) | ||||||
end | ||||||
|
||||||
def comment? | ||||||
parse_options = { comment_char: "#", standalone_comments: false } | ||||||
|
||||||
->(data) { parse_comment_line(data, parse_options).first.empty? } | ||||||
end | ||||||
|
||||||
def parse_data | ||||||
->(data) { [data.split[0], data.split[1], data.split[1..-1]] } | ||||||
end | ||||||
|
||||||
private | ||||||
|
||||||
def read_content | ||||||
f = inspec.file(@path) | ||||||
# Test if the path exist and that it's a file | ||||||
if f.file? | ||||||
# Retrieve the file's contents | ||||||
f.content | ||||||
else | ||||||
# If the file doesn't exist, skip all tests that use gordon_config | ||||||
raise Inspec::Exceptions::ResourceSkipped, "Can't read config at #{@path}" | ||||||
def format_data | ||||||
->(data) { %w{ip_address primary_name all_host_names}.zip(data).to_h } | ||||||
end | ||||||
end | ||||||
end | ||||||
``` | ||||||
|
||||||
Let's break down each component of the resource. | ||||||
If you've ever done object-oriented programming, you might be seeing some familiar concepts in this resource file. Let's break down some of the subcomponents of the resource. | ||||||
|
||||||
#### require (lines 1 and 2) | ||||||
The `require` statement pulls in code written in other files or Ruby modules that we will use in this resource. In this case we are importing some simple utility functions defined elsewhere in the InSpec codebase. | ||||||
#### class | ||||||
The class is where the Ruby file is defined. | ||||||
The `class` is where a Ruby class definition is given. Classes define the structure and function of an object that we can instantiate to model something. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. put the stuff about inheritance and the api version in here under a "details" expando thing |
||||||
#### name | ||||||
The name is how we will call upon this resource within our controls, in the example above that would be `gordon_config`. | ||||||
The `name` defines what token we can use to invoke this resource within our controls. Remember all those `describe` blocks we wrote that invoked the `nginx` resource? We used the term `nginx` to invoke the resource because that token is the defined `name` of the resource in its class definition. | ||||||
#### supports | ||||||
Supports are used to define or restrict the Ruby resource to work in specific ways, as shown in the example above that is used to restrict our class to specific platforms. | ||||||
The `supports` keyword is used to define what types of system should be able to use this resource.or restrict the Ruby resource to work in specific ways, as shown in the example above that is used to restrict our class to specific platforms. | ||||||
wdower marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
#### desc | ||||||
A simple description of the purpose of this resource. | ||||||
A simple human-friendly description of the purpose of this resource. This is also what gets printed when you run `<resource> help` in the InSpec shell. | ||||||
wdower marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
#### examples | ||||||
A simple use case example. The example is usually a `describe` block using the resource, given as a multi-line comment. | ||||||
A simple use case example or a set of them. The example is usually a `describe` block using the resource, given as a multi-line comment. | ||||||
wdower marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
#### private | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is just straight up ruby syntax. we should probably minimize our explanation here and link out to official docs, and then we can remove most of the explanation cause it honestly is not really relevant to resource development (though it is relevant for software engineering/development in general) |
||||||
This is a keyword that asserts that every function definition that shows up below this line in the class file should be considered _private_, or not accessible to users who instantiate an object out of this class. For example, when using this resource in a control file, you cannot invoke the `parse_data` function, because it is a private function that should really only be invoked by the resource class itself when the object is first created. | ||||||
#### initialize method | ||||||
An initilize method is required if your resource needs to be able to accept a parameter when called in a test (e.g. `file('this/path/is/a/parameter')`) | ||||||
An `initialize` method is required if your resource needs to be able to accept a parameter when called in a test (e.g. `file('this/path/is/a/parameter')`). | ||||||
wdower marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
#### functionality methods | ||||||
These methods return data from the resource so that you can use it in tests. | ||||||
These methods return data from the resource so that you can use it in tests. There can be just a few of them, or there can be a whole bunch. These methods are how we define the custom matchers that can be invoked in an InSpec control file. We'll build some simple examples in the next section. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this author section necessary? I feel like this and the "contributor" section at the bottom are often just completely wrong by now esp considering how much group authorship and editing has been going on which is unattributed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more like an @aaronlippold question
Non-blocker for this PR