-
Notifications
You must be signed in to change notification settings - Fork 90
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
Plugins: Test harness, test fixture, docs, and local-type example #356
Merged
Merged
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
b089042
Rename train-gem-fixture to train-test-fixture
clintoncwolfe 2a38731
Update test fixture plugin to support platform forcing and basic conn…
clintoncwolfe 7451be7
First draft of docs and examples
clintoncwolfe 7d9e779
train-test-fixture feature complete
clintoncwolfe 2f472ee
Top-level files for train-local-rot13
clintoncwolfe 2a906a9
Unit tests pass
clintoncwolfe e057109
Add fixture files
clintoncwolfe 9a03772
Another bonkers plugin helper.
clintoncwolfe 5807deb
Passing functional tests
clintoncwolfe ffa3590
Enable using Train project rubocop config
clintoncwolfe d9c6816
Linting
clintoncwolfe 1910356
PR feedback
clintoncwolfe f2af2fb
Correct unit test expected value
clintoncwolfe c2727ad
Add plugin transport doc info.
jquick 4c8072f
Fix feedback.
jquick d1f3c07
Fix lint issues. And disable UTF-8 force since its forced on ruby 2.0.
jquick 53f56ec
Fix ruby 2.2 heredoc format.
jquick File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
# Train Plugins | ||
|
||
## Introducing Plugins | ||
|
||
Train plugins are a way to add new transports and platform detection to Train. | ||
|
||
If you are familiar with InSpec plugins, be forewarned; the two plugin systems are not similar. | ||
|
||
### Why Plugins? | ||
|
||
#### Benefits of plugins | ||
|
||
Plugins address two main needs that the Chef InSpec Engineering team (which maintains Train) encountered in 2017-2018: | ||
|
||
* Passionate contributor parties can develop and release new Train transports at their own pace, without gating by Chef engineers. | ||
* Reduction of dependency bloat within Train. For example, since only the AWS transport needs the aws-sdk, we can move the gem dependency into the plugin gem, and out of train itself. | ||
|
||
#### Future of existing Transports | ||
|
||
The Chef InSpec Engineering team currently (October 2018) plans to migrate most existing Train transports into plugins. For example, AWS, Azure, and GCP are all excellent candidates for migration to plugin status. The team commits to keeping SSH, WinRM, and Local transports in Train core. All other transports may be migrated. | ||
|
||
In the near-term, InSpec will carry a gemspec dependency on the migrated plugins. This will continue a smooth experience for users relying on (for example) Azure. | ||
|
||
## Managing Plugins | ||
|
||
### Installing and Managing Train Plugins as an InSpec User | ||
|
||
InSpec has a command-line plugin management interface, which is used for managing both InSpec plugins and Train plugins. For example, to install `train-aws`, simply run: | ||
|
||
```bash | ||
$ inspec plugin install train-aws | ||
``` | ||
|
||
The management facility can install, update, and remove plugins, including their dependencies. | ||
|
||
### Installing Train Plugins outside of InSpec | ||
|
||
If you need a train plugin installed, and `inspec plugin` is not available to you, you can install a train plugin like any other gem. Just be sure to use the `gem` binary that comes with the application you wish to extend. For example, to add a Train Plugin to a ChefDK installation, use: | ||
|
||
```bash | ||
$ chef exec gem install train-something | ||
``` | ||
|
||
### Finding Train plugins | ||
|
||
Train plugins can be found by running: | ||
|
||
```bash | ||
$ inspec plugin search train- | ||
``` | ||
|
||
If you are not an InSpec user, you may also perform a RubyGems search: | ||
|
||
```bash | ||
$ gem search train- | ||
``` | ||
|
||
## Developing Train Plugins for the Train Plugin API v1 | ||
|
||
Train plugins are gems. Their names must start with 'train-'. | ||
|
||
You can use the example plugin at [the Train github project](https://github.com/inspec/train/tree/master/examples/train-local-rot13) as a starting point. | ||
|
||
### The Entry Point | ||
|
||
As with any Gem library, you should create a file with the name of your plugin, which loads the remaining files you need. Some plugins place them in 1 file, but it is cleaner to place them in 4: a version file, then transport, connection and platform files. | ||
|
||
### The Transport File | ||
|
||
In this file, you should define a class that inherits from `Train.plugin(1)`. The class returned will be `Train::Plugins::Transport` or a descendant. This superclass provides DSL methods, abstract methods, instance variables, and accessors for you to configure your plugin. | ||
|
||
Feedback about providing a clearer Plugin API for a future Plugin V2 API is welcome. | ||
|
||
#### `name` DSL method | ||
|
||
Required. Use the `name` call to register your plugin. Pass a String, which should have the 'train-' portion removed. | ||
|
||
#### `option` DSL method | ||
|
||
The option method is used to register new information into your transport options hash. This hash contains all the information your transport will need for its connection and runtime support. These options calls are a good place to pull in defaults or information from environment variables. | ||
|
||
#### @options Instance Variable | ||
|
||
This variable includes any options you passed in from the DSL method when defining a transport. It will also merge in any options passed from the URL definition for your transport (schema, host, etc). | ||
|
||
#### `connection` abstract method | ||
|
||
Required to be implemented. Called with a single arg which is usually ignored. You must return an instance of a class that is a descendant of `Train::Plugins::Transports::BaseConnection`. Typically you will call the constructor with the `@options`. | ||
|
||
### Connection File | ||
|
||
The your Connection class must inherit from `Train::Plugins::Transports::BaseConnection`. Abstract methods it should implement include: | ||
|
||
#### initialize | ||
|
||
Not required but is a good place to set option defaults for options that were passed with the transport URL. Example: | ||
|
||
```Ruby | ||
def initialize(options) | ||
# Override for cli region from url host | ||
# aws://region/my-profile | ||
options[:region] = options[:host] if options.key?(:host) | ||
super(options) | ||
end | ||
``` | ||
|
||
#### run_command_via_connection | ||
|
||
If your transport is OS based and has the option to read a file you can set this method. It is expected to return a `Train::File::Remote::*` class here to be used upstream in InSpec. Currently the file resource is restricted to Unix and Windows platforms. Caching is enabled by default for this method. | ||
|
||
#### file_via_connection | ||
|
||
If your transport is OS based and has the option to run a command you can set this method. It is expected to return a `CommandResult` class here to be used upstream in InSpec. Currently the command resource is restricted to Unix and Windows platforms. Caching is enabled by default for this method. | ||
|
||
#### API Access Methods | ||
|
||
When working with API's it's often helpful to create methods to return client information or API objects. These are then accessed upstream in InSpec. Here is an example of a API method you may have: | ||
|
||
```Ruby | ||
def aws_client(klass) | ||
return klass.new unless cache_enabled?(:api_call) | ||
@cache[:api_call][klass.to_s.to_sym] ||= klass.new | ||
end | ||
``` | ||
|
||
This will return a class and cache the client object accordingly if caching is enabled. You can call this from a inspec resource by calling `inspec.backend.aws_client(AWS::TEST::CLASS)`. | ||
|
||
#### local? | ||
|
||
This flag helps Train decide what detection to use for OS based platforms. This should be set to `true` if your transport target resides in the same instance you are running train from. This setting is not needed for API transports or transports that do not use platform detection. | ||
|
||
#### platform | ||
|
||
`platform` is called when InSpec is trying to detect the platform (OS family, etc). We recommend that you implement platform in a separate Module, and include it. | ||
|
||
### Platform Detection | ||
|
||
Platform detection is used if you do not specify a platform method for your transport. Currently it is only used for OS (Unix, Windows) platforms. The detection system will run a series of commands on your target to try and determine what platform it is. This information can be found here [OS Specifications](https://github.com/inspec/train/blob/master/lib/train/platforms/detect/specifications/os.rb). | ||
|
||
When using an API or a fixed platform for your transport it's suggested you skip the detection process and specify a direct platform. Here is an example: | ||
|
||
```Ruby | ||
def platform | ||
Train::Platforms.name('Aws').in_family('cloud') | ||
force_platform!('Aws', | ||
release: '1.2', | ||
) | ||
end | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# encoding: utf-8 | ||
source 'https://rubygems.org' | ||
|
||
# This is Gemfile, which is used by bundler | ||
# to ensure a coherent set of gems is installed. | ||
# This file lists dependencies needed when outside | ||
# of a gem (the gemspec lists deps for gem deployment) | ||
|
||
# Bundler should refer to the gemspec for any dependencies. | ||
gemspec | ||
|
||
# Remaining group is only used for development. | ||
group :development do | ||
gem 'bundler' | ||
gem 'byebug' | ||
gem 'inspec', '>= 2.2.112' # We need InSpec for the test harness while developing. | ||
gem 'minitest' | ||
gem 'rake' | ||
gem 'rubocop', '= 0.49.1' # Need to keep in sync with main InSpec project, so config files will work | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Copyright (c) 2018 Chef Software Inc. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Example Train Plugin - train-local-rot13 | ||
|
||
This plugin is provided as a teaching example for building a Train plugin. Train plugins allow you to connect to remote systems or APIs, so that other tools such as InSpec or Chef Workstation can talk over the connection. | ||
clintoncwolfe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
train-local-rot13's functionality is simple: it acts as a local transport (targeting the local machine), but it applies the [rot13](https://en.wikipedia.org/wiki/ROT13) trivial cypher transformation on the contents of each file it reads, and on the stdout of every command it executes. | ||
|
||
Please note that ROT13 is an incredibly weak cypher, and can be broken by most elementary school students. Do not use this plugin for security purposes. | ||
|
||
## Relationship between InSpec and Train | ||
|
||
Train itself has no CLI, nor a sophisticated test harness. InSpec does have such facilities, so installing Train plugins will require an InSpec installation. You do not need to use or understand InSpec. | ||
|
||
Train plugins may be developed without an InSpec installation. | ||
|
||
## To Install this as a User | ||
|
||
You will need InSpec v2.3 or later. | ||
|
||
If you just want to use this (not learn how to write a plugin), you can so by simply running: | ||
|
||
``` | ||
$ inspec plugin install train-local-rot13 | ||
``` | ||
|
||
You can then run: | ||
|
||
``` | ||
$ inspec detect -t local-rot13:// | ||
== Platform Details | ||
Name: local-rot13 | ||
Families: unix, os, windows, os | ||
Release: 0.1.0 | ||
Arch: example | ||
$ inspec shell -t local-rot13:// -c 'command("echo hello")' | ||
uryyb | ||
``` | ||
|
||
## Features of This Example Kit | ||
|
||
This example plugin is a full-fledged plugin example, with everything a real-world, industrial grade plugin would have, including: | ||
|
||
* an implementation of a Train plugin, using the Train Plugin V1 API, including | ||
* a Transport | ||
* a Connection | ||
* Platform configuration | ||
* documentation (you are reading it now) | ||
* tests, at the unit and functional level | ||
* a .gemspec, for packaging and publishing it as a gem | ||
* a Gemfile, for managing its dependencies | ||
* a Rakefile, for running development tasks | ||
* Rubocop linting support for using the base Train project rubocop.yml (See Rakefile) | ||
|
||
You are encouraged to use this plugin as a starting point for real plugins. | ||
|
||
## Development of a Plugin | ||
|
||
[Plugin Development](https://github.com/inspec/train/blob/master/docs/dev/plugins.md) is documented on the `train` project on GitHub. Additionally, this example | ||
plugin has extensive comments explaining what is happening, and why. | ||
|
||
### A Tour of the Plugin | ||
|
||
One nice circuit of the plugin might be: | ||
* look at the gemspec, to see what the plugin thinks it does | ||
* look at the functional tests, to see the plugin proving it does what it says | ||
* look at the unit tests, to see how the plugin claims it is internally structured | ||
* look at the Rakefile, to see how to interact with the project | ||
* look at lib/train-local-rot13.rb, the entry point which InSpec will always load if the plugin is installed | ||
* look at lib/train-local-rot13/transport.rb, the plugin "backbone" | ||
* look at lib/train-local-rot13/connection.rb, the plugin implementation | ||
* look at lib/train-local-rot13/platform.rb, OS platform support declaration |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# A Rakefile defines tasks to help maintain your project. | ||
# Rake provides several task templates that are useful. | ||
|
||
#------------------------------------------------------------------# | ||
# Test Runner Tasks | ||
#------------------------------------------------------------------# | ||
|
||
# This task template will make a task named 'test', and run | ||
# the tests that it finds. | ||
require 'rake/testtask' | ||
|
||
Rake::TestTask.new do |t| | ||
t.libs.push 'lib' | ||
t.test_files = FileList[ | ||
'test/unit/*_test.rb', | ||
'test/integration/*_test.rb', | ||
'test/function/*_test.rb', | ||
] | ||
t.verbose = true | ||
# Ideally, we'd run tests with warnings enabled, | ||
# but the dependent gems have many warnings. As this | ||
# is an example, let's disable them so the testing | ||
# experience is cleaner. | ||
t.warning = false | ||
end | ||
|
||
#------------------------------------------------------------------# | ||
# Code Style Tasks | ||
#------------------------------------------------------------------# | ||
require 'rubocop/rake_task' | ||
|
||
RuboCop::RakeTask.new(:lint) do |t| | ||
# Choices of rubocop rules to enforce are deeply personal. | ||
# Here, we set things up so that your plugin will use the Bundler-installed | ||
# train gem's copy of the Train project's rubocop.yml file (which | ||
clintoncwolfe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# is indeed packaged with the train gem). | ||
clintoncwolfe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
require 'train/globals' | ||
train_rubocop_yml = File.join(Train.src_root, '.rubocop.yml') | ||
|
||
t.options = ['--display-cop-names', '--config', train_rubocop_yml] | ||
end |
21 changes: 21 additions & 0 deletions
21
examples/plugins/train-local-rot13/lib/train-local-rot13.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# This file is known as the "entry point." | ||
# This is the file Train will try to load if it | ||
# thinks your plugin is needed. | ||
|
||
# The *only* thing this file should do is setup the | ||
# load path, then load plugin files. | ||
|
||
# Next two lines simply add the path of the gem to the load path. | ||
# This is not needed when being loaded as a gem; but when doing | ||
# plugin development, you may need it. Either way, it's harmless. | ||
libdir = File.dirname(__FILE__) | ||
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) | ||
|
||
# It's traditonal to keep your gem version in a separate file, so CI can find it easier. | ||
require 'train-local-rot13/version' | ||
|
||
# A train plugin has three components: Transport, Connection, and Platform. | ||
# Transport acts as the glue. | ||
require 'train-local-rot13/transport' | ||
require 'train-local-rot13/platform' | ||
require 'train-local-rot13/connection' |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I'm not sure we support adding gems to
ChefDK
. It is intended to be a bundled distribution.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.
@jquick?
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.
ChefDK should always have
train
but the example is fine imo.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.
I only worry that a user will add a gem and pull in a dep that will break ChefDK in some way.
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.
If deps have issues it should not install and error just like a bundle but retain functionality. If they want to install a new Train on their DevKit that is their call.