kitchen-binding 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .ruby-version
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kitchen-vagrant.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rake'
8
+ end
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,6 @@
1
+
2
+ require 'kitchen/binding/core_ext/util'
3
+ require 'kitchen/binding/core_ext/config'
4
+ require 'kitchen/binding/core_ext/data_munger'
5
+ require 'kitchen/binding/core_ext/instance'
6
+ require 'kitchen/binding/core_ext/ssh_base'
@@ -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: []