sfn-serverspec 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/LICENSE +13 -0
- data/README.md +79 -0
- data/lib/sfn-serverspec.rb +2 -0
- data/lib/sfn-serverspec/validator.rb +183 -0
- data/lib/sfn-serverspec/version.rb +5 -0
- data/sfn-serverspec.gemspec +18 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3b8afe3d9e73b49ff634ee667f86b20e64ee3d7b
|
4
|
+
data.tar.gz: d99d8bc34c182d9e732b62eb190035ca472195e7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9beffad9fb727f1fd25a4429d52ffe04b9859f4cfbd5c0c0c68d2da706f35fe7c7b03873f1642fa8d742670538e792c19b5caeb2e4f391db26899809ed8e48a4
|
7
|
+
data.tar.gz: 2271aac2052a33acd71c7cf9539fec2d26f9449c25af33636a4efb69d357f575c5242a22ff39096a7453f1b5103e3ae830e11123d1e34d99174855cd2c3a8b0c
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2015 Heavy Water Operations LLC.
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# SparkleFormation ServerSpec Callback
|
2
|
+
|
3
|
+
sfn-serverspec is a [callback](http://www.sparkleformation.io/docs/sfn/callbacks.html) for [sfn](https://github.com/sparkleformation/sfn) which executes [Serverspec](http://serverspec.org) assertions against stack compute resources after successful stack creation.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
### Enable
|
8
|
+
|
9
|
+
Callbacks are configured via the `.sfn` configuration file. First, the callback must be enabled:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Configuration.new do
|
13
|
+
callbacks do
|
14
|
+
require ['sfn-serverspec']
|
15
|
+
default ['serverspec_validator']
|
16
|
+
end
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
### Configure
|
21
|
+
|
22
|
+
Some configuration of callback behavior is available via the `.sfn` file:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
Configuration.new do
|
26
|
+
sfn_serverspec do
|
27
|
+
global_spec_patterns [File.join(Dir.pwd, 'spec/base/*_spec.rb')]
|
28
|
+
ssh_proxy_command 'ssh ec2-user@server.example.com nc %h %p'
|
29
|
+
ssh_user 'ubuntu'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
All of the following settings are optional:
|
35
|
+
|
36
|
+
* `global_spec_patterns` - Array of strings specifying file paths for specs, defaults to `[]`
|
37
|
+
* `ssh_proxy_command` - String passed as the [proxy command for remote SSH connection](http://continuousimprovement.me/code/2014/12/03/serverspec-behind-jump-server.html) to the target compute resource, defaults to `nil`
|
38
|
+
* `ssh_user` - Username for remote SSH connection, defaults to `ec2-user`
|
39
|
+
* `ssh_port` - Port for remote SSH connection, defaults to `22`
|
40
|
+
* `ssh_key_paths` - Array of strings describing paths to one or more ssh private keys, defaults to `[]`
|
41
|
+
* `ssh_key_passphrase` - String used as passphrase for any encrypted ssh private keys defined in `ssh_key_paths`, defaults to `nil`
|
42
|
+
|
43
|
+
Additional configuration may be provided in a SparkleFormation template at the resource level:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
resources(:my_cool_app_asg) do
|
47
|
+
type 'AWS::AutoScaling::AutoScalingGroup'
|
48
|
+
properties do
|
49
|
+
# ...
|
50
|
+
end
|
51
|
+
serverspec do
|
52
|
+
spec_patterns [File.join(Dir.pwd, '../spec/my_cool_app/*_spec.rb')]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
For each resource with a `serverspec` block defined, the resource-level value of `spec_patterns` is combined with the value of `global_spec_patterns` from the .sfn config file to yield a list of specs which will be executed against a given resource.
|
58
|
+
|
59
|
+
The following configuration options specified at the resource level will override the same options specified in the .sfn config file:
|
60
|
+
|
61
|
+
* `ssh_user`
|
62
|
+
* `ssh_port`
|
63
|
+
* `ssh_proxy_command`
|
64
|
+
* `ssh_key_paths`
|
65
|
+
* `ssh_key_passphrase`
|
66
|
+
|
67
|
+
|
68
|
+
## Caveats
|
69
|
+
|
70
|
+
Providing `serverspec` configuration on compute resources works in a manner similar to sfn's built-in Stack Policy callback. Specifically, the `serverspec` key and its contents are removed from the template during compile, cached by the callback and executed after completion of stack creation.
|
71
|
+
|
72
|
+
As of this writing, the callback only processes Serverspec configuration on `AWS::AutoScaling::AutoScalingGroup` and `AWS::EC2::Instance` resources.
|
73
|
+
|
74
|
+
Non-compute resources with `serverspec` configuration will not be processed by this callback, and will probably cause a validation error when trying to validate or create a stack from the template.
|
75
|
+
|
76
|
+
## Info
|
77
|
+
|
78
|
+
* Repository: [https://github.com/sparkleformation/sfn-serverspec](https://github.com/sparkleformation/sfn-serverspec)
|
79
|
+
* IRC: Freenode @ #sparkleformation
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'net/ssh/proxy/command'
|
4
|
+
require 'rspec'
|
5
|
+
require 'rspec/core/formatters/documentation_formatter'
|
6
|
+
require 'serverspec'
|
7
|
+
require 'sfn'
|
8
|
+
|
9
|
+
module Sfn
|
10
|
+
# This is an sfn callback
|
11
|
+
class Callback
|
12
|
+
# Validate stack resources against Serverspec assertions
|
13
|
+
class ServerspecValidator < Callback
|
14
|
+
# @return [Smash] cached policies
|
15
|
+
attr_reader :policies
|
16
|
+
|
17
|
+
# Overload to init policy cache
|
18
|
+
#
|
19
|
+
# @return [self]
|
20
|
+
def initialize(*args)
|
21
|
+
super
|
22
|
+
@policies = Smash.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def after_create(*args)
|
26
|
+
log = Logger.new(STDOUT)
|
27
|
+
log.level = Logger.const_get(
|
28
|
+
config.fetch(:sfn_serverspec, :log_level, :info).to_s.upcase
|
29
|
+
)
|
30
|
+
|
31
|
+
policies.each do |resource, r_config|
|
32
|
+
resource_config = r_config.dump!.to_smash(:snake)
|
33
|
+
|
34
|
+
ssh_proxy_command = resource_config.fetch(
|
35
|
+
:ssh_proxy_command,
|
36
|
+
config.fetch(:sfn_serverspec, :ssh_proxy_command, nil)
|
37
|
+
)
|
38
|
+
|
39
|
+
ssh_key_paths = [
|
40
|
+
resource_config.fetch(
|
41
|
+
:ssh_key_paths,
|
42
|
+
config.fetch(:sfn_serverspec, :ssh_key_paths, nil)
|
43
|
+
)
|
44
|
+
].flatten.compact
|
45
|
+
|
46
|
+
ssh_key_passphrase = resource_config.fetch(
|
47
|
+
:ssh_key_passphrase,
|
48
|
+
config.fetch(:sfn_serverspec, :ssh_key_passphrase, nil)
|
49
|
+
)
|
50
|
+
|
51
|
+
instances = expand_compute_resource(args.first[:api_stack], resource)
|
52
|
+
|
53
|
+
instances.each do |instance|
|
54
|
+
target_host = case ssh_proxy_command.nil?
|
55
|
+
when true
|
56
|
+
instance.addresses_public.first.address
|
57
|
+
when false
|
58
|
+
instance.addresses_private.first.address
|
59
|
+
end
|
60
|
+
|
61
|
+
begin
|
62
|
+
rspec_config = RSpec.configuration
|
63
|
+
rspec_config.reset
|
64
|
+
rspec_config.reset_filters
|
65
|
+
RSpec.world.reset
|
66
|
+
|
67
|
+
rspec_formatter = RSpec::Core::Formatters::DocumentationFormatter.new(
|
68
|
+
rspec_config.output_stream
|
69
|
+
)
|
70
|
+
|
71
|
+
rspec_reporter = RSpec::Core::Reporter.new(rspec_config)
|
72
|
+
|
73
|
+
rspec_config.instance_variable_set(:@reporter, rspec_reporter)
|
74
|
+
rspec_loader = rspec_config.send(:formatter_loader)
|
75
|
+
rspec_notifications = rspec_loader.send(
|
76
|
+
:notifications_for,
|
77
|
+
RSpec::Core::Formatters::DocumentationFormatter
|
78
|
+
)
|
79
|
+
rspec_reporter.register_listener(
|
80
|
+
rspec_formatter,
|
81
|
+
*rspec_notifications
|
82
|
+
)
|
83
|
+
|
84
|
+
global_specs = [
|
85
|
+
config.fetch(:sfn_serverspec, :global_spec_patterns, [])
|
86
|
+
].flatten.compact
|
87
|
+
|
88
|
+
resource_specs = [
|
89
|
+
resource_config.fetch(:spec_patterns, [])
|
90
|
+
].flatten.compact
|
91
|
+
|
92
|
+
spec_patterns = global_specs + resource_specs
|
93
|
+
|
94
|
+
log.debug "spec loading patterns: #{spec_patterns.inspect}"
|
95
|
+
log.debug "using SSH proxy commmand #{ssh_proxy_command}" unless ssh_proxy_command.nil?
|
96
|
+
log.info "running specs against #{target_host}"
|
97
|
+
|
98
|
+
Specinfra.configuration.backend :ssh
|
99
|
+
Specinfra.configuration.request_pty true
|
100
|
+
Specinfra.configuration.host target_host
|
101
|
+
|
102
|
+
connection_options = {
|
103
|
+
user: resource_config.fetch(
|
104
|
+
:ssh_user,
|
105
|
+
config.fetch(:sfn_serverspec, :ssh_user, 'ec2-user')
|
106
|
+
),
|
107
|
+
port: resource_config.fetch(
|
108
|
+
:ssh_port,
|
109
|
+
config.fetch(:sfn_serverspec, :ssh_port, 22)
|
110
|
+
)
|
111
|
+
}
|
112
|
+
|
113
|
+
unless ssh_proxy_command.nil?
|
114
|
+
connection_options[:proxy] = Net::SSH::Proxy::Command.new(ssh_proxy_command)
|
115
|
+
log.debug "using ssh proxy command: #{ssh_proxy_command}"
|
116
|
+
end
|
117
|
+
|
118
|
+
unless ssh_key_paths.empty?
|
119
|
+
connection_options[:keys] = ssh_key_paths
|
120
|
+
log.debug "using ssh key paths #{connection_options[:keys]} exclusively"
|
121
|
+
end
|
122
|
+
|
123
|
+
unless ssh_key_passphrase.nil?
|
124
|
+
connection_options[:passphrase] = ssh_key_passphrase
|
125
|
+
end
|
126
|
+
|
127
|
+
Specinfra.configuration.ssh_options connection_options
|
128
|
+
|
129
|
+
RSpec::Core::Runner.run(spec_patterns.map { |p| Dir.glob(p) })
|
130
|
+
|
131
|
+
rescue => e
|
132
|
+
log.error "Something unexpected happened when running rspec: #{e.inspect}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
COMPUTE_RESOURCE_TYPES = ['AWS::EC2::Instance', 'AWS::AutoScaling::AutoScalingGroup']
|
140
|
+
|
141
|
+
# Generate policy for stack, collate policies in cache
|
142
|
+
#
|
143
|
+
# @return [nil]
|
144
|
+
def template(info)
|
145
|
+
compiled_stack = info[:sparkle_stack].compile
|
146
|
+
|
147
|
+
compiled_stack.resources.keys!.each do |r|
|
148
|
+
r_object = compiled_stack.resources[r]
|
149
|
+
if COMPUTE_RESOURCE_TYPES.include?(r_object['Type']) && r_object['Serverspec']
|
150
|
+
@policies.set(r, r_object.delete!('Serverspec'))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
# look up stack resource by name, return array of expanded compute instances
|
158
|
+
#
|
159
|
+
# @param stack [Miasma::Models::Orchestration::Stack]
|
160
|
+
# @param name [String]
|
161
|
+
# @return [Array<Miasma::Models::Compute::Server>]
|
162
|
+
def expand_compute_resource(stack, name)
|
163
|
+
compute_resource = stack.resources.all.detect do |resource|
|
164
|
+
resource.logical_id == name
|
165
|
+
end
|
166
|
+
|
167
|
+
if compute_resource.within?(:compute, :server)
|
168
|
+
[compute_resource.instance.expand]
|
169
|
+
else
|
170
|
+
compute_resource.expand.servers.map(&:expand)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Override the `#set` method provided by Serverspec to ensure any
|
177
|
+
# `spec_helper.rb` files do not clobber our configuration setup
|
178
|
+
|
179
|
+
alias :serverspec_set :set
|
180
|
+
|
181
|
+
def set(*args)
|
182
|
+
serverspec_set(args.first)
|
183
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
|
2
|
+
require 'sfn-serverspec/version'
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'sfn-serverspec'
|
5
|
+
s.version = SfnServerspec::VERSION.version
|
6
|
+
s.summary = 'Executes Serverspec assertions against stack compute resources'
|
7
|
+
s.author = 'Heavy Water Operations'
|
8
|
+
s.email = 'support@heavywater.io'
|
9
|
+
s.homepage = 'http://github.com/sparkleformation/sfn-serverspec'
|
10
|
+
s.description = 'Executes Serverspec assertions against stack compute resources'
|
11
|
+
s.license = 'Apache-2.0'
|
12
|
+
s.require_path = 'lib'
|
13
|
+
s.add_runtime_dependency 'sfn', '>= 1.0.0', '< 2.0'
|
14
|
+
s.add_runtime_dependency 'serverspec', '~> 2.24'
|
15
|
+
s.add_development_dependency 'rake'
|
16
|
+
s.add_development_dependency 'rubocop', '~> 0.35.0'
|
17
|
+
s.files = Dir['{lib,bin,docs}/**/*'] + %w(sfn-serverspec.gemspec README.md CHANGELOG.md LICENSE)
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sfn-serverspec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Heavy Water Operations
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sfn
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.0.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '2.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: serverspec
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.24'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.24'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rubocop
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.35.0
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 0.35.0
|
75
|
+
description: Executes Serverspec assertions against stack compute resources
|
76
|
+
email: support@heavywater.io
|
77
|
+
executables: []
|
78
|
+
extensions: []
|
79
|
+
extra_rdoc_files: []
|
80
|
+
files:
|
81
|
+
- CHANGELOG.md
|
82
|
+
- LICENSE
|
83
|
+
- README.md
|
84
|
+
- lib/sfn-serverspec.rb
|
85
|
+
- lib/sfn-serverspec/validator.rb
|
86
|
+
- lib/sfn-serverspec/version.rb
|
87
|
+
- sfn-serverspec.gemspec
|
88
|
+
homepage: http://github.com/sparkleformation/sfn-serverspec
|
89
|
+
licenses:
|
90
|
+
- Apache-2.0
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.4.8
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Executes Serverspec assertions against stack compute resources
|
112
|
+
test_files: []
|
113
|
+
has_rdoc:
|