sfn-serverspec 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,2 @@
1
+ # v0.1.0
2
+ * Initial release
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,2 @@
1
+ require 'sfn-serverspec/version'
2
+ require 'sfn-serverspec/validator'
@@ -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,5 @@
1
+ # SfnServerspec internal constants
2
+ module SfnServerspec
3
+ # Current version
4
+ VERSION = Gem::Version.new('0.1.0')
5
+ 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: