kitchen-binding 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +8 -0
- data/LICENSE +15 -0
- data/README.md +100 -0
- data/kitchen-binding.gemspec +21 -0
- data/lib/kitchen/binding.rb +50 -0
- data/lib/kitchen/binding/base.rb +203 -0
- data/lib/kitchen/binding/core_ext.rb +6 -0
- data/lib/kitchen/binding/core_ext/config.rb +76 -0
- data/lib/kitchen/binding/core_ext/data_munger.rb +38 -0
- data/lib/kitchen/binding/core_ext/instance.rb +44 -0
- data/lib/kitchen/binding/core_ext/ssh_base.rb +59 -0
- data/lib/kitchen/binding/core_ext/util.rb +147 -0
- data/lib/kitchen/binding/pry_remote.rb +72 -0
- metadata +92 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Author:: Jacob McCann (<jmccann.git@gmail.com>)
|
2
|
+
|
3
|
+
Copyright (C) 2014, Jacob McCann
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
kitchen-binding
|
2
|
+
===============
|
3
|
+
|
4
|
+
kitchen-binding is an extension to [test-kitchen](https://github.com/test-kitchen/test-kitchen) to allow setting breakpoints in your cookbooks. When encountered during a converge test-kitchen will then login to an interactive ruby shell for your debugging pleasure. When you are finished the converge will continue where it left of. It will continue to pickup any other breakpoints you may have setup through the run as well.
|
5
|
+
|
6
|
+
A product from discussion @ [Chef Summit 2014](https://github.com/opscode/chef-summit-2014/wiki/friday_metropolitan_1000)
|
7
|
+
|
8
|
+
Try It Out
|
9
|
+
----------
|
10
|
+
Check out what it does with the help of my cookbook [tk-bindings](https://github.com/jmccann/tk-bindings). Make sure to adhear to the [Pre-Setup for Ruby](https://github.com/jmccann/kitchen-binding#ruby-version-dependencies) below first. The other items are already in the .kitchen.yml for tk-bindings.
|
11
|
+
|
12
|
+
Pre-Setup
|
13
|
+
---------
|
14
|
+
|
15
|
+
Currently there is some required pre-setup you will need to do in order to use this extension.
|
16
|
+
|
17
|
+
### Instance Networking
|
18
|
+
|
19
|
+
The instance must currently have a 33.33.33.200 IP accessbile from the host as this is currently hardcoded. This is one of the first things I plan on working to address.
|
20
|
+
|
21
|
+
#### Vagrant
|
22
|
+
If you are using a vagrant driver you will need to have a virtual private newtwork setup on the guest with an IP. This is a requirement for the default binding plugin 'pry-remote' as pry-remote's dependencies use RPC which eventually uses a random port that you can not dynamically create a port forward for (or atleast not easily). An idea to make this easier would be to setup a virtual private network for all of your vagrant instances by setting the following in your ~/.kitchen/config.yml
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
driver:
|
26
|
+
network:
|
27
|
+
- ["private_network", {ip: "33.33.33.200"}]
|
28
|
+
```
|
29
|
+
|
30
|
+
### Ruby Version Dependencies
|
31
|
+
The version of uby you use on your host system must match the version of ruby being used by Chef in the instance. This is ruby 1.9.3-p547 for Chef 11. This is a requirement because DRB libraries used by 'pry-remote' do not seem compatible across ruby versions.
|
32
|
+
|
33
|
+
This can be easily controlled by using a ruby version/environment manager.
|
34
|
+
|
35
|
+
#### rbenv
|
36
|
+
If you use rbenv with ruby-build
|
37
|
+
|
38
|
+
Install needed ruby:
|
39
|
+
```
|
40
|
+
rbenv install 1.9.3-p547
|
41
|
+
```
|
42
|
+
|
43
|
+
Then while inside the repo for the cookbook you want to test pin the repo version specifically for that cookbook:
|
44
|
+
```
|
45
|
+
rbenv local 1.9.3-p547
|
46
|
+
```
|
47
|
+
|
48
|
+
Then you'll probably need to install bundler and/or the required gems:
|
49
|
+
```
|
50
|
+
gem install bundler
|
51
|
+
bundle install
|
52
|
+
```
|
53
|
+
|
54
|
+
How to Use
|
55
|
+
----------
|
56
|
+
|
57
|
+
The default remote binding supported is [pry-remote](https://github.com/Mon-Ouie/pry-remote). Add anywhere in your Chef rubies code the following:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
require 'pry-remote'
|
61
|
+
binding.remote_pry '0.0.0.0'
|
62
|
+
```
|
63
|
+
|
64
|
+
This will insert a breakpoint into the code that will start a pry-remote server listening on all addresses (by default it listens only on localhost).
|
65
|
+
|
66
|
+
Setup your Gemfile for your cookbook with the line:
|
67
|
+
|
68
|
+
```
|
69
|
+
gem 'kitchen-binding'
|
70
|
+
```
|
71
|
+
|
72
|
+
Then finally you need to add some ERB to your .kitchen.yml to load the library:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
# <% require 'kitchen/binding' %>
|
76
|
+
# <% require 'kitchen/binding/base' %>
|
77
|
+
```
|
78
|
+
|
79
|
+
The above could also be added to your ~/.kitchen/config.yml if you wanted it always available.
|
80
|
+
|
81
|
+
Then do a kitchen converge doing `bundle exec kitchen converge` and if the breakpoint is hit you should get dropped into an interactive ruby shell.
|
82
|
+
|
83
|
+
Contributing
|
84
|
+
------------
|
85
|
+
* Source hosted at [GitHub](https://github.com/jmccann/kitchen-binding)
|
86
|
+
* Report issues/questions/feature requests on [GitHub Issues](https://github.com/jmccann/kitchen-binding/issues)
|
87
|
+
|
88
|
+
Pull requests are very welcome! Make sure your patches are well tested.
|
89
|
+
Ideally create a topic branch for every separate change you make. For
|
90
|
+
example:
|
91
|
+
|
92
|
+
1. Fork the repo
|
93
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
94
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
95
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
96
|
+
5. Create new Pull Request
|
97
|
+
|
98
|
+
Authors
|
99
|
+
-------
|
100
|
+
Created and maintained by Jacob McCann (<jmccann.git@gmail.com>)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'kitchen-binding'
|
7
|
+
gem.version = '0.2.0'
|
8
|
+
gem.license = 'Apache 2.0'
|
9
|
+
gem.authors = ['Jacob McCann']
|
10
|
+
gem.email = ['jmcann.git@gmail.com']
|
11
|
+
gem.description = 'Test Kitchen extension for remote ruby shells'
|
12
|
+
gem.summary = gem.description
|
13
|
+
gem.homepage = 'https://github.com/jmccann/kitchen-binding/'
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = []
|
17
|
+
gem.require_paths = ['lib']
|
18
|
+
|
19
|
+
gem.add_dependency 'test-kitchen', '~> 1.2.1'
|
20
|
+
gem.add_dependency 'pry-remote', '~> 0.1.8'
|
21
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Jacob McCann (<jmccann.git@gmail.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2014, Jacob McCann
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
require 'thor/util'
|
20
|
+
|
21
|
+
module Kitchen
|
22
|
+
# A binding is responsible for generating the commands necessary to
|
23
|
+
# install, set up and use a interactive ruby shell for debugging called
|
24
|
+
# from a Provisioner (e.g. Chef Cookbook during Chef Run)
|
25
|
+
#
|
26
|
+
# @author Jacob McCann <jmccann.git@gmail.com>
|
27
|
+
module Binding
|
28
|
+
# Default provisioner to use
|
29
|
+
DEFAULT_PLUGIN = 'pry_remote'.freeze
|
30
|
+
|
31
|
+
# Returns an instance of a binding given a plugin type string.
|
32
|
+
#
|
33
|
+
# @param plugin [String] a binding plugin type, to be constantized
|
34
|
+
# @param config [Hash] a configuration hash to initialize the provisioner
|
35
|
+
# @return [Binding::Base] a driver instance
|
36
|
+
# @raise [ClientError] if a provisioner instance could not be created
|
37
|
+
def self.for_plugin(plugin, config)
|
38
|
+
require("kitchen/binding/#{plugin}")
|
39
|
+
|
40
|
+
str_const = Thor::Util.camel_case(plugin)
|
41
|
+
klass = const_get(str_const)
|
42
|
+
klass.new(config)
|
43
|
+
rescue LoadError, NameError
|
44
|
+
raise ClientError,
|
45
|
+
"Could not load the '#{plugin}' binding from the load path." \
|
46
|
+
" Please ensure that your binding is installed as a gem or" \
|
47
|
+
" included in your Gemfile if using Bundler."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Jacob McCann (<jmccann.git@gmail.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2014, Jacob McCann
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
# require 'kitchen/configurable'
|
20
|
+
|
21
|
+
require 'socket'
|
22
|
+
|
23
|
+
require 'kitchen/binding/core_ext'
|
24
|
+
require 'kitchen/lazy_hash'
|
25
|
+
|
26
|
+
module Kitchen
|
27
|
+
module Binding
|
28
|
+
# Base class for a binding.
|
29
|
+
#
|
30
|
+
# @author Jacob McCann <jmccann.git@gmail.com>
|
31
|
+
class Base
|
32
|
+
# require 'pry'
|
33
|
+
# binding.pry
|
34
|
+
|
35
|
+
# include Configurable # Available in edge test-kitchen
|
36
|
+
include Logging
|
37
|
+
|
38
|
+
attr_accessor :state
|
39
|
+
|
40
|
+
attr_accessor :instance # Not required in edge
|
41
|
+
|
42
|
+
# Create a new Binding object using the provided configuration data
|
43
|
+
# which will be merged with any default configuration.
|
44
|
+
#
|
45
|
+
# @param config [Hash] provided binding configuration
|
46
|
+
def initialize(config = {})
|
47
|
+
# init_config(config) # Available in edge
|
48
|
+
|
49
|
+
# Following not required in edge test-kitchen
|
50
|
+
@config = LazyHash.new(config, self)
|
51
|
+
self.class.defaults.each do |attr, value|
|
52
|
+
@config[attr] = value unless @config.has_key?(attr)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Not required in edge
|
57
|
+
def instance=(instance)
|
58
|
+
@instance = instance
|
59
|
+
expand_paths!
|
60
|
+
load_needed_dependencies!
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the name of this transport, suitable for display in a CLI.
|
64
|
+
#
|
65
|
+
# @return [String] name of this transport
|
66
|
+
def name
|
67
|
+
self.class.name.split('::').last.downcase
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the command that will install the remote interactive ruby shell.
|
71
|
+
#
|
72
|
+
# @return [String] the command to install the remote ruby shell
|
73
|
+
# @raise [ActionFailed] if the action could not be completed
|
74
|
+
def install_command
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the command that will log into a remote interactive ruby shell.
|
78
|
+
#
|
79
|
+
# @raise [ActionFailed] if the action could not be completed
|
80
|
+
def connect_command
|
81
|
+
end
|
82
|
+
|
83
|
+
# Conditionally prefixes a command with a sudo command.
|
84
|
+
#
|
85
|
+
# @param command [String] command to be prefixed
|
86
|
+
# @return [String] the command, conditionaly prefixed with sudo
|
87
|
+
# @api private
|
88
|
+
def sudo(script)
|
89
|
+
config[:sudo] ? "sudo -E #{script}" : script
|
90
|
+
end
|
91
|
+
|
92
|
+
# Test a remote port's connectivity.
|
93
|
+
#
|
94
|
+
# @return [true,false] a truthy value if the connection is ready
|
95
|
+
# and false otherwise
|
96
|
+
def test_connection
|
97
|
+
debug("Testing binding connection <#{hostname}:#{port}>")
|
98
|
+
socket = TCPSocket.new(hostname, port)
|
99
|
+
IO.select([socket], nil, nil, 5)
|
100
|
+
true
|
101
|
+
rescue *SOCKET_EXCEPTIONS, Errno::EPERM, Errno::ETIMEDOUT
|
102
|
+
debug("No binding server detected")
|
103
|
+
sleep config[:binding_timeout] || 2
|
104
|
+
false
|
105
|
+
ensure
|
106
|
+
socket && socket.close
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
attr_reader :config # Not required in edge
|
112
|
+
|
113
|
+
# TCP socket exceptions
|
114
|
+
SOCKET_EXCEPTIONS = [
|
115
|
+
SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
116
|
+
Errno::ENETUNREACH, IOError
|
117
|
+
]
|
118
|
+
|
119
|
+
# @return [Hash] Transport options
|
120
|
+
attr_reader :options
|
121
|
+
|
122
|
+
# @return [Logger] the logger to use
|
123
|
+
# @api private
|
124
|
+
attr_reader :logger
|
125
|
+
|
126
|
+
# @return [Integer] Binding port
|
127
|
+
# @api private
|
128
|
+
def port
|
129
|
+
config[:port]
|
130
|
+
end
|
131
|
+
|
132
|
+
# @return [String] Binding hostname
|
133
|
+
# @api private
|
134
|
+
def hostname
|
135
|
+
'33.33.33.200'
|
136
|
+
end
|
137
|
+
|
138
|
+
# String representation of object, reporting its connection details and
|
139
|
+
# configuration.
|
140
|
+
#
|
141
|
+
# @api private
|
142
|
+
def to_s
|
143
|
+
"#{hostname}:#{port}<#{config.inspect}>"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns a suitable logger to use for output.
|
147
|
+
#
|
148
|
+
# @return [Kitchen::Logger] a logger
|
149
|
+
def logger
|
150
|
+
instance ? instance.logger : Kitchen.logger
|
151
|
+
end
|
152
|
+
|
153
|
+
# Intercepts any bare #puts calls in subclasses and issues an INFO log
|
154
|
+
# event instead.
|
155
|
+
#
|
156
|
+
# @param msg [String] message string
|
157
|
+
def puts(msg) # rubocop:disable Lint/UnusedMethodArgument
|
158
|
+
info(msg)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Intercepts any bare #print calls in subclasses and issues an INFO log
|
162
|
+
# event instead.
|
163
|
+
#
|
164
|
+
# @param msg [String] message string
|
165
|
+
def print(msg) # rubocop:disable Lint/UnusedMethodArgument
|
166
|
+
info(msg)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Adds http and https proxy environment variables to a command, if set
|
170
|
+
# in configuration data.
|
171
|
+
#
|
172
|
+
# @param command [String] command string
|
173
|
+
# @return [String] command string
|
174
|
+
# @api private
|
175
|
+
def env_command(command) # rubocop:disable Lint/UnusedMethodArgument
|
176
|
+
end
|
177
|
+
|
178
|
+
# Not required in edge
|
179
|
+
def self.defaults
|
180
|
+
@defaults ||= Hash.new.merge(super_defaults)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Not required in edge
|
184
|
+
def self.super_defaults
|
185
|
+
klass = self.superclass
|
186
|
+
|
187
|
+
if klass.respond_to?(:defaults)
|
188
|
+
klass.defaults
|
189
|
+
else
|
190
|
+
Hash.new
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Not required in edge
|
195
|
+
def self.default_config(attr, value = nil, &block)
|
196
|
+
defaults[attr] = block_given? ? block : value
|
197
|
+
end
|
198
|
+
|
199
|
+
# Can be moved to top later ... moved to bottom for current test-kitchen
|
200
|
+
default_config :port, 5000
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Jacob McCann (<jmccann.git@gmail.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2014, Jacob McCann
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
module Kitchen
|
20
|
+
# Override certain methods from Kitchen::Config to allow use of
|
21
|
+
# Kitchen::Binding
|
22
|
+
class Config
|
23
|
+
# Generates the immutable Test Kitchen configuration and reasonable
|
24
|
+
# defaults for Drivers, Provisioners and Bindings.
|
25
|
+
#
|
26
|
+
# @return [Hash] a configuration Hash
|
27
|
+
# @api private
|
28
|
+
def kitchen_config
|
29
|
+
@kitchen_config ||= {
|
30
|
+
:defaults => {
|
31
|
+
:driver => Driver::DEFAULT_PLUGIN,
|
32
|
+
:provisioner => Provisioner::DEFAULT_PLUGIN,
|
33
|
+
:binding => Binding::DEFAULT_PLUGIN
|
34
|
+
},
|
35
|
+
:kitchen_root => kitchen_root,
|
36
|
+
:test_base_path => test_base_path,
|
37
|
+
:log_level => log_level
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Builds a newly configured Instance object, for a given Suite and
|
42
|
+
# Platform.
|
43
|
+
#
|
44
|
+
# @param suite [Suite,#name] a Suite
|
45
|
+
# @param platform [Platform,#name] a Platform
|
46
|
+
# @param index [Integer] an index used for colorizing output
|
47
|
+
# @return [Instance] a new Instance object
|
48
|
+
# @api private
|
49
|
+
def new_instance(suite, platform, index)
|
50
|
+
Instance.new(
|
51
|
+
:busser => new_busser(suite, platform),
|
52
|
+
:driver => new_driver(suite, platform),
|
53
|
+
:logger => new_logger(suite, platform, index),
|
54
|
+
:suite => suite,
|
55
|
+
:platform => platform,
|
56
|
+
:provisioner => new_provisioner(suite, platform),
|
57
|
+
:binding => new_binding(suite, platform),
|
58
|
+
:state_file => new_state_file(suite, platform)
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Builds a newly configured Binding object, for a given Suite and
|
65
|
+
# Platform.
|
66
|
+
#
|
67
|
+
# @param suite [Suite,#name] a Suite
|
68
|
+
# @param platform [Platform,#name] a Platform
|
69
|
+
# @return [Binding] a new Binding object
|
70
|
+
# @api private
|
71
|
+
def new_binding(suite, platform)
|
72
|
+
bdata = data.binding_data_for(suite.name, platform.name)
|
73
|
+
Binding.for_plugin(bdata[:name], bdata)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Jacob McCann (<jmccann.git@gmail.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2014, Jacob McCann
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
module Kitchen
|
20
|
+
# Add some additioanl methods to Kitchen::DataMunger to allow use of
|
21
|
+
# Kitchen::Binding
|
22
|
+
class DataMunger
|
23
|
+
# Generate a new Hash of configuration data that can be used to construct
|
24
|
+
# a new Binding object.
|
25
|
+
#
|
26
|
+
# @param suite [String] a suite name
|
27
|
+
# @param platform [String] a platform name
|
28
|
+
# @return [Hash] a new configuration Hash that can be used to construct a
|
29
|
+
# new Binding
|
30
|
+
def binding_data_for(suite, platform)
|
31
|
+
merged_data_for(:binding, suite, platform).tap do |bdata|
|
32
|
+
set_kitchen_config_at!(bdata, :kitchen_root)
|
33
|
+
set_kitchen_config_at!(bdata, :test_base_path)
|
34
|
+
set_kitchen_config_at!(bdata, :log_level)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Jacob McCann (<jmccann.git@gmail.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2014, Jacob McCann
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
module Kitchen
|
20
|
+
# Add some additioanl methods to Kitchen::Instnace to allow use of
|
21
|
+
# Kitchen::Binding
|
22
|
+
class Instance
|
23
|
+
# @return [Binding::Base] binding object which will the setup
|
24
|
+
# and invocation instructions for remote interactive ruby shells
|
25
|
+
attr_reader :binding
|
26
|
+
|
27
|
+
def initialize(options = {})
|
28
|
+
validate_options(options)
|
29
|
+
|
30
|
+
@suite = options.fetch(:suite)
|
31
|
+
@platform = options.fetch(:platform)
|
32
|
+
@name = self.class.name_for(@suite, @platform)
|
33
|
+
@driver = options.fetch(:driver)
|
34
|
+
@provisioner = options.fetch(:provisioner)
|
35
|
+
@binding = options.fetch(:binding)
|
36
|
+
@busser = options.fetch(:busser)
|
37
|
+
@logger = options.fetch(:logger) { Kitchen.logger }
|
38
|
+
@state_file = options.fetch(:state_file)
|
39
|
+
|
40
|
+
setup_driver
|
41
|
+
setup_provisioner
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Jacob McCann (<jmccann.git@gmail.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2014, Jacob McCann
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
module Kitchen
|
20
|
+
module Driver
|
21
|
+
# Override converge method from Kitchen::Driver::SSHBase to allow use of
|
22
|
+
# Kitchen::Binding
|
23
|
+
class SSHBase < Base
|
24
|
+
# Overriding to add watching Chef Converge and connecting to
|
25
|
+
# binding if started
|
26
|
+
def converge(state)
|
27
|
+
provisioner = instance.provisioner
|
28
|
+
provisioner.create_sandbox
|
29
|
+
sandbox_dirs = Dir.glob("#{provisioner.sandbox_path}/*")
|
30
|
+
|
31
|
+
binding = instance.binding
|
32
|
+
binding.state = state
|
33
|
+
|
34
|
+
Kitchen::SSH.new(*build_ssh_args(state)) do |conn|
|
35
|
+
run_remote(provisioner.install_command, conn)
|
36
|
+
run_remote(binding.install_command, conn)
|
37
|
+
|
38
|
+
run_remote(provisioner.init_command, conn)
|
39
|
+
transfer_path(sandbox_dirs, provisioner[:root_path], conn)
|
40
|
+
run_remote(provisioner.prepare_command, conn)
|
41
|
+
|
42
|
+
# Run converge in a thread
|
43
|
+
chef_run_thread = Thread.new do
|
44
|
+
run_remote(provisioner.run_command, conn)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Endlessly check until converge has completed
|
48
|
+
while chef_run_thread.alive?
|
49
|
+
debug("Chef thread still running")
|
50
|
+
debug("Binding server running?: #{binding.test_connection}")
|
51
|
+
binding.connect_command if binding.test_connection
|
52
|
+
end
|
53
|
+
end
|
54
|
+
ensure
|
55
|
+
provisioner && provisioner.cleanup_sandbox
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2012, 2013, 2014, Fletcher Nichol
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
module Kitchen
|
20
|
+
|
21
|
+
# Stateless utility methods used in different contexts. Essentially a mini
|
22
|
+
# PassiveSupport library.
|
23
|
+
#
|
24
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
25
|
+
module Util
|
26
|
+
|
27
|
+
# # Returns the standard library Logger level constants for a given symbol
|
28
|
+
# # representation.
|
29
|
+
# #
|
30
|
+
# # @param symbol [Symbol] symbol representation of a logger level (:debug,
|
31
|
+
# # :info, :warn, :error, :fatal)
|
32
|
+
# # @return [Integer] Logger::Severity constant value or nil if input is not
|
33
|
+
# # valid
|
34
|
+
# def self.to_logger_level(symbol)
|
35
|
+
# return nil unless [:debug, :info, :warn, :error, :fatal].include?(symbol)
|
36
|
+
|
37
|
+
# Logger.const_get(symbol.to_s.upcase)
|
38
|
+
# end
|
39
|
+
|
40
|
+
# # Returns the symbol represenation of a logging levels for a given
|
41
|
+
# # standard library Logger::Severity constant.
|
42
|
+
# #
|
43
|
+
# # @param const [Integer] Logger::Severity constant value for a logging
|
44
|
+
# # level (Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR,
|
45
|
+
# # Logger::FATAL)
|
46
|
+
# # @return [Symbol] symbol representation of the logging level
|
47
|
+
# def self.from_logger_level(const)
|
48
|
+
# case const
|
49
|
+
# when Logger::DEBUG then :debug
|
50
|
+
# when Logger::INFO then :info
|
51
|
+
# when Logger::WARN then :warn
|
52
|
+
# when Logger::ERROR then :error
|
53
|
+
# else :fatal
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
|
57
|
+
# # Returns a new Hash with all key values coerced to symbols. All keys
|
58
|
+
# # within a Hash are coerced by calling #to_sym and hashes within arrays
|
59
|
+
# # and other hashes are traversed.
|
60
|
+
# #
|
61
|
+
# # @param obj [Object] the hash to be processed. While intended for
|
62
|
+
# # hashes, this method safely processes arbitrary objects
|
63
|
+
# # @return [Object] a converted hash with all keys as symbols
|
64
|
+
# def self.symbolized_hash(obj)
|
65
|
+
# if obj.is_a?(Hash)
|
66
|
+
# obj.inject({}) { |h, (k, v)| h[k.to_sym] = symbolized_hash(v); h }
|
67
|
+
# elsif obj.is_a?(Array)
|
68
|
+
# obj.inject([]) { |a, e| a << symbolized_hash(e); a }
|
69
|
+
# else
|
70
|
+
# obj
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
|
74
|
+
# # Returns a new Hash with all key values coerced to strings. All keys
|
75
|
+
# # within a Hash are coerced by calling #to_s and hashes with arrays
|
76
|
+
# # and other hashes are traversed.
|
77
|
+
# #
|
78
|
+
# # @param obj [Object] the hash to be processed. While intended for
|
79
|
+
# # hashes, this method safely processes arbitrary objects
|
80
|
+
# # @return [Object] a converted hash with all keys as strings
|
81
|
+
# def self.stringified_hash(obj)
|
82
|
+
# if obj.is_a?(Hash)
|
83
|
+
# obj.inject({}) { |h, (k, v)| h[k.to_s] = stringified_hash(v); h }
|
84
|
+
# elsif obj.is_a?(Array)
|
85
|
+
# obj.inject([]) { |a, e| a << stringified_hash(e); a }
|
86
|
+
# else
|
87
|
+
# obj
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
|
91
|
+
# # Returns a formatted string representing a duration in seconds.
|
92
|
+
# #
|
93
|
+
# # @param total [Integer] the total number of seconds
|
94
|
+
# # @return [String] a formatted string of the form (XmYY.00s)
|
95
|
+
# def self.duration(total)
|
96
|
+
# total = 0 if total.nil?
|
97
|
+
# minutes = (total / 60).to_i
|
98
|
+
# seconds = (total - (minutes * 60))
|
99
|
+
# format("(%dm%.2fs)", minutes, seconds)
|
100
|
+
# end
|
101
|
+
|
102
|
+
# Generates a command (or series of commands) wrapped so that it can be
|
103
|
+
# invoked on a remote instance or locally.
|
104
|
+
#
|
105
|
+
# This method uses the Bourne shell (/bin/sh) to maximize the chance of
|
106
|
+
# cross platform portability on Unixlike systems.
|
107
|
+
#
|
108
|
+
# @param [String] the command
|
109
|
+
# @return [String] a wrapped command string
|
110
|
+
def self.wrap_command(cmd)
|
111
|
+
cmd = "false" if cmd.nil?
|
112
|
+
cmd = "true" if cmd.to_s.empty?
|
113
|
+
cmd = cmd.sub(/\n\Z/, "") if cmd =~ /\n\Z/
|
114
|
+
|
115
|
+
"sh -c '\n#{cmd}\n'"
|
116
|
+
end
|
117
|
+
|
118
|
+
# # Modifes the given string to strip leading whitespace on each line, the
|
119
|
+
# # amount which is calculated by using the first line of text.
|
120
|
+
# #
|
121
|
+
# # @example
|
122
|
+
# #
|
123
|
+
# # string = <<-STRING
|
124
|
+
# # a
|
125
|
+
# # b
|
126
|
+
# # c
|
127
|
+
# # STRING
|
128
|
+
# # Util.outdent!(string) # => "a\n b\nc\n"
|
129
|
+
# #
|
130
|
+
# # @param string [String] the string that will be modified
|
131
|
+
# # @return [String] the modified string
|
132
|
+
# def self.outdent!(string)
|
133
|
+
# string.gsub!(/^ {#{string.index(/[^ ]/)}}/, "")
|
134
|
+
# end
|
135
|
+
|
136
|
+
# # Returns a set of Bourne Shell (AKA /bin/sh) compatible helper
|
137
|
+
# # functions. This function is usually called inline in a string that
|
138
|
+
# # will be executed remotely on a test instance.
|
139
|
+
# #
|
140
|
+
# # @return [String] a string representation of useful helper functions
|
141
|
+
# def self.shell_helpers
|
142
|
+
# IO.read(File.join(
|
143
|
+
# File.dirname(__FILE__), %w[.. .. support download_helpers.sh]
|
144
|
+
# ))
|
145
|
+
# end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Jacob McCann (<jmccann.git@gmail.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2014, Jacob McCann
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
module Kitchen
|
20
|
+
module Binding
|
21
|
+
# pry-remote binding
|
22
|
+
#
|
23
|
+
# @author Jacob McCann <jmccann.git@gmail.com>
|
24
|
+
class PryRemote < Base
|
25
|
+
default_config :port, 9876
|
26
|
+
default_config :sudo, true
|
27
|
+
|
28
|
+
# (see Base#install_command)
|
29
|
+
def install_command
|
30
|
+
<<-INSTALL.gsub(/^ {10}/, "")
|
31
|
+
if [[ $(/opt/chef/embedded/bin/gem list | grep -c pry-remote) -eq 0 ]]; then
|
32
|
+
echo "-----> Installing Binding (pry-remote)"
|
33
|
+
#{sudo(install_string)}
|
34
|
+
else
|
35
|
+
echo "-----> Binding (pry-remote) installation detected"
|
36
|
+
fi
|
37
|
+
INSTALL
|
38
|
+
end
|
39
|
+
|
40
|
+
# (see Base#connect_command)
|
41
|
+
def connect_command
|
42
|
+
info("Connecting to remote binding")
|
43
|
+
require 'pry-remote'
|
44
|
+
::Pry.config.input = STDIN
|
45
|
+
::Pry.config.output = STDOUT
|
46
|
+
|
47
|
+
argv = ['--server', hostname, '--port', port.to_s]
|
48
|
+
|
49
|
+
client = ::PryRemote::CLI.new argv
|
50
|
+
client.run
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def install_string
|
56
|
+
level = config[:log_level] == :info ? :auto : config[:log_level]
|
57
|
+
|
58
|
+
cmd = '/opt/chef/embedded/bin/gem install pry-remote'
|
59
|
+
args = [
|
60
|
+
'--no-rdoc',
|
61
|
+
'--no-ri'
|
62
|
+
]
|
63
|
+
args << '--debug' if level == :debug
|
64
|
+
|
65
|
+
cmd = [cmd, *args].join(' ')
|
66
|
+
debug("Remote binding install command: #{cmd}")
|
67
|
+
|
68
|
+
cmd
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kitchen-binding
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jacob McCann
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-10-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: test-kitchen
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.2.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.2.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: pry-remote
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.1.8
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.1.8
|
46
|
+
description: Test Kitchen extension for remote ruby shells
|
47
|
+
email:
|
48
|
+
- jmcann.git@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE
|
56
|
+
- README.md
|
57
|
+
- kitchen-binding.gemspec
|
58
|
+
- lib/kitchen/binding.rb
|
59
|
+
- lib/kitchen/binding/base.rb
|
60
|
+
- lib/kitchen/binding/core_ext.rb
|
61
|
+
- lib/kitchen/binding/core_ext/config.rb
|
62
|
+
- lib/kitchen/binding/core_ext/data_munger.rb
|
63
|
+
- lib/kitchen/binding/core_ext/instance.rb
|
64
|
+
- lib/kitchen/binding/core_ext/ssh_base.rb
|
65
|
+
- lib/kitchen/binding/core_ext/util.rb
|
66
|
+
- lib/kitchen/binding/pry_remote.rb
|
67
|
+
homepage: https://github.com/jmccann/kitchen-binding/
|
68
|
+
licenses:
|
69
|
+
- Apache 2.0
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.23.2
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Test Kitchen extension for remote ruby shells
|
92
|
+
test_files: []
|