nodespec 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +90 -0
  5. data/LICENSE.md +21 -0
  6. data/README.md +107 -0
  7. data/Rakefile +5 -0
  8. data/lib/nodespec/backend_proxy/base.rb +20 -0
  9. data/lib/nodespec/backend_proxy/cmd.rb +9 -0
  10. data/lib/nodespec/backend_proxy/exec.rb +21 -0
  11. data/lib/nodespec/backend_proxy/ssh.rb +28 -0
  12. data/lib/nodespec/backend_proxy/unixshell_utility.rb +32 -0
  13. data/lib/nodespec/backend_proxy/winrm.rb +23 -0
  14. data/lib/nodespec/backends.rb +13 -0
  15. data/lib/nodespec/command_execution.rb +16 -0
  16. data/lib/nodespec/communication_adapters/aws_ec2.rb +24 -0
  17. data/lib/nodespec/communication_adapters/local_backend.rb +15 -0
  18. data/lib/nodespec/communication_adapters/native_communicator.rb +32 -0
  19. data/lib/nodespec/communication_adapters/remote_backend.rb +15 -0
  20. data/lib/nodespec/communication_adapters/ssh.rb +14 -0
  21. data/lib/nodespec/communication_adapters/ssh_communicator.rb +54 -0
  22. data/lib/nodespec/communication_adapters/vagrant.rb +37 -0
  23. data/lib/nodespec/communication_adapters/winrm.rb +13 -0
  24. data/lib/nodespec/communication_adapters/winrm_communicator.rb +65 -0
  25. data/lib/nodespec/communication_adapters.rb +22 -0
  26. data/lib/nodespec/local_command_runner.rb +17 -0
  27. data/lib/nodespec/node.rb +56 -0
  28. data/lib/nodespec/node_configurations.rb +29 -0
  29. data/lib/nodespec/provisioning/ansible.rb +96 -0
  30. data/lib/nodespec/provisioning/chef.rb +68 -0
  31. data/lib/nodespec/provisioning/puppet.rb +55 -0
  32. data/lib/nodespec/provisioning/shellscript.rb +19 -0
  33. data/lib/nodespec/provisioning.rb +14 -0
  34. data/lib/nodespec/run_options.rb +14 -0
  35. data/lib/nodespec/runtime_gem_loader.rb +19 -0
  36. data/lib/nodespec/shared_examples_support.rb +13 -0
  37. data/lib/nodespec/verbose_output.rb +9 -0
  38. data/lib/nodespec/version.rb +3 -0
  39. data/nodespec.gemspec +28 -0
  40. data/spec/backend_proxy/base_spec.rb +29 -0
  41. data/spec/backend_proxy/exec_spec.rb +34 -0
  42. data/spec/backend_proxy/ssh_spec.rb +32 -0
  43. data/spec/backend_proxy/unixshell_utility_spec.rb +29 -0
  44. data/spec/backend_proxy/winrm_spec.rb +34 -0
  45. data/spec/command_execution_spec.rb +36 -0
  46. data/spec/communication_adapters/aws_ec2_spec.rb +70 -0
  47. data/spec/communication_adapters/local_backend_spec.rb +38 -0
  48. data/spec/communication_adapters/native_communicator_spec.rb +53 -0
  49. data/spec/communication_adapters/remote_backend_spec.rb +46 -0
  50. data/spec/communication_adapters/ssh_communicator_spec.rb +121 -0
  51. data/spec/communication_adapters/ssh_spec.rb +18 -0
  52. data/spec/communication_adapters/vagrant_spec.rb +61 -0
  53. data/spec/communication_adapters/winrm_communicator_spec.rb +111 -0
  54. data/spec/communication_adapters/winrm_spec.rb +18 -0
  55. data/spec/communication_adapters_spec.rb +29 -0
  56. data/spec/local_command_runner_spec.rb +26 -0
  57. data/spec/node_configurations_spec.rb +41 -0
  58. data/spec/node_spec.rb +110 -0
  59. data/spec/provisioning/ansible_spec.rb +143 -0
  60. data/spec/provisioning/chef_spec.rb +87 -0
  61. data/spec/provisioning/puppet_spec.rb +54 -0
  62. data/spec/provisioning/shellscript_spec.rb +20 -0
  63. data/spec/provisioning_spec.rb +52 -0
  64. data/spec/runtime_gem_loader_spec.rb +33 -0
  65. data/spec/spec_helper.rb +5 -0
  66. data/spec/support/backend.rb +10 -0
  67. data/spec/support/init_with_current_node.rb +4 -0
  68. data/spec/support/local_command.rb +12 -0
  69. data/spec/support/node_command.rb +12 -0
  70. data/spec/support/ssh_communicator.rb +9 -0
  71. data/spec/support/winrm_communicator.rb +9 -0
  72. metadata +105 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 22522efb82f175e6aa68df1948c065319324497d
4
- data.tar.gz: 40053aea1394efc150ca04382e3e302fe7286197
3
+ metadata.gz: be7328ed95144b05ecb62ff63b1bd38101d626ae
4
+ data.tar.gz: fd27d50ae085de35513cdef5cf12e4b6223e9180
5
5
  SHA512:
6
- metadata.gz: fb1ad96f278e3e8337c978a4bd7b16aaacb1444ca5441de2227ad3dd5a929cc3cdca3c55a941f93e0ca3ce9f8e16bbd5af0dc0f71bb9e7595ace6937dee27a55
7
- data.tar.gz: 1606e98c8e5b57a5163a5e12a2f6030f6f41c5fc613518490eaeaa4e7da15c346e90ac74ca7924a065d2ff7422bd8744f133c097c28369f3f4e3d18c817778ee
6
+ metadata.gz: 38a821a5b1a5db66e89d88aa04b751e34a432dfcbba50eee6dc294a59b493863628507a1a060cb93012557ac6adb433eb431aa1e5aef08c6df54cd540e7e02bd
7
+ data.tar.gz: 50f522db6244b8509713abffc8adf6f07e4c6abb1908f6ed6597af760b6062c31201492e5bc4dfda8bdd38118bd5246c3ad8aaeebb51f6d7d7c7cea4c04f38b8
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .rspec
2
+ .ruby-version
3
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,90 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nodespec (0.1.9)
5
+ net-ssh
6
+ serverspec
7
+ specinfra (>= 1.18.4)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ akami (1.2.2)
13
+ gyoku (>= 0.4.0)
14
+ nokogiri
15
+ aws-sdk (1.44.0)
16
+ json (~> 1.4)
17
+ nokogiri (>= 1.4.4)
18
+ builder (3.2.2)
19
+ diff-lcs (1.2.5)
20
+ ffi (1.9.3)
21
+ gssapi (1.0.3)
22
+ ffi (>= 1.0.1)
23
+ gyoku (1.1.1)
24
+ builder (>= 2.1.2)
25
+ highline (1.6.21)
26
+ httpclient (2.4.0)
27
+ httpi (0.9.7)
28
+ rack
29
+ json (1.8.1)
30
+ little-plugger (1.1.3)
31
+ logging (1.8.2)
32
+ little-plugger (>= 1.1.3)
33
+ multi_json (>= 1.8.4)
34
+ mini_portile (0.6.0)
35
+ multi_json (1.10.1)
36
+ net-ssh (2.9.1)
37
+ nokogiri (1.6.2.1)
38
+ mini_portile (= 0.6.0)
39
+ nori (1.1.5)
40
+ rack (1.5.2)
41
+ rake (10.3.2)
42
+ rspec (3.0.0)
43
+ rspec-core (~> 3.0.0)
44
+ rspec-expectations (~> 3.0.0)
45
+ rspec-mocks (~> 3.0.0)
46
+ rspec-core (3.0.2)
47
+ rspec-support (~> 3.0.0)
48
+ rspec-expectations (3.0.2)
49
+ diff-lcs (>= 1.2.0, < 2.0)
50
+ rspec-support (~> 3.0.0)
51
+ rspec-mocks (3.0.2)
52
+ rspec-support (~> 3.0.0)
53
+ rspec-support (3.0.2)
54
+ rubyntlm (0.1.1)
55
+ savon (0.9.5)
56
+ akami (~> 1.0)
57
+ builder (>= 2.1.2)
58
+ gyoku (>= 0.4.0)
59
+ httpi (~> 0.9)
60
+ nokogiri (>= 1.4.0)
61
+ nori (~> 1.0)
62
+ wasabi (~> 1.0)
63
+ serverspec (0.15.4)
64
+ highline
65
+ net-ssh
66
+ rspec (>= 2.13.0)
67
+ specinfra (>= 0.7.1)
68
+ specinfra (1.22.0)
69
+ uuidtools (2.1.4)
70
+ wasabi (1.0.0)
71
+ nokogiri (>= 1.4.0)
72
+ winrm (1.1.3)
73
+ gssapi (~> 1.0.0)
74
+ httpclient (~> 2.2, >= 2.2.0.2)
75
+ logging (~> 1.6, >= 1.6.1)
76
+ nokogiri (~> 1.5)
77
+ rubyntlm (~> 0.1.1)
78
+ savon (= 0.9.5)
79
+ uuidtools (~> 2.1.2)
80
+
81
+ PLATFORMS
82
+ ruby
83
+
84
+ DEPENDENCIES
85
+ aws-sdk
86
+ bundler
87
+ nodespec!
88
+ rake
89
+ rspec (~> 3.0)
90
+ winrm
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Silvio Montanari
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ nodespec
2
+ ========
3
+
4
+ RSpec style tests for multiple nodes/server instances with support for provisioning with puppet, chef, ansible
5
+
6
+ ## Nodespec vs Serverspec
7
+ [Serverspec](http://serverspec.org) is a popular gem that allows you to write rspec tests to validate your servers/hosts configuration.
8
+
9
+ Nodespec is not an alternative to Serverspec, rather it's build on top of it, so you can still leverage all of its features while enjoying some extra goodies.
10
+
11
+ #### What's different
12
+ Nodespec overcomes some of the limitations of Serverspec, such as mixed OS' support (Windows and UN*X), multiple backends (Ssh and Winrm) and easy configurability and connectivity to your target hosts.
13
+
14
+ Nodespec enables a declarative way of configuring your remote connections, be it simple Ssh, a Vagrant box, or an Amazon EC2 instance.
15
+
16
+ Nodespec adds support for issuing provisiong commands to your target hosts, that could be incorporated as part of your test setup.
17
+
18
+ Below is a quick summary of the main features of nodespec. Refer to the [wiki](https://github.com/smontanari/nodespec/wiki) for more details and examples.
19
+
20
+ ## Nodespec features
21
+
22
+ #### Hostname declared or inferred in specs
23
+
24
+ In serverspec you would typically have to create folders named after your target servers, whereas in nodespec you can simply declare your target server names as your spec example group:
25
+
26
+ ```ruby
27
+ describe "test.example1.com" do
28
+ ...
29
+ end
30
+ ```
31
+ ```ruby
32
+ describe "test.example2.com" do
33
+ ...
34
+ end
35
+ ```
36
+ #### Easy connection configuration
37
+ No more ruby code and create programmatically SSH objects, just a quick and easy inline (or file/yaml based) configuration:
38
+
39
+ ```ruby
40
+ describe "test.example1.com", nodespec: {
41
+ 'adapter' => 'ssh',
42
+ 'user' => 'testuser',
43
+ 'keys' => 'path/to/key'
44
+ } do
45
+ ...
46
+ end
47
+ ```
48
+ #### Support connections to Windows & Un*x servers
49
+ One of the major limitations of serverspec is that you have to make a hard decision beforehand on which OS/backend you are targeting with your tests. In particular you have to include specific specinfra modules in your `spec_helper` depending on whether you're connecting to Un\*x or Windows machines, and you cannot test both OS as part of the same spec run (unless you start hacking some conditional logic in your spec_helper, that is).
50
+
51
+ With Nodespec that problem is resolved and you can easily connect and test multiple OS and multiple backends in the same rspec run. For instance, to connect to a windows box:
52
+ ```ruby
53
+ describe "test.example2.com", nodespec: {
54
+ 'os' => 'Windows',
55
+ 'adapter' => 'winrm',
56
+ 'user' => 'testuser',
57
+ 'pass' => 'somepass',
58
+ 'transport' => 'ssl'
59
+ 'basic_auth_only' => true
60
+ } do
61
+ ...
62
+ end
63
+ ```
64
+ ## Provisioning (aka TDD your infrastructure)
65
+ Nodespec provides support for running Chef, Puppet, Ansible or shellscript commands as part of your tests, e.g.:
66
+
67
+ #### Chef
68
+ ```ruby
69
+ describe "server1", nodespec: {'adapter' => 'vagrant'} do
70
+ before :all do
71
+ provision_node_with_chef do
72
+ set_cookbook_paths '/vshared/src/cookbooks'
73
+ set_attributes demo: {crontab: {user: 'peter'}}
74
+ chef_client_runlist 'demo::folders', 'demo::crontab'
75
+ end
76
+ end
77
+ ...
78
+ end
79
+ ```
80
+
81
+ #### Puppet
82
+ ```ruby
83
+ describe "server1", nodespec: {'adapter' => 'vagrant'} do
84
+ before :all do
85
+ provision_node_with_puppet do
86
+ set_modulepaths '/vshared/src/modules'
87
+ set_hieradata('users' => {'roger' => {'uid' => 5801}, 'peter' => {'uid' => 5802}})
88
+ puppet_apply_execute "include demo::wheel_users"
89
+ end
90
+ end
91
+ ...
92
+ end
93
+ ```
94
+
95
+ #### Ansible
96
+ ```ruby
97
+ describe "i-8f5e74r9", nodespec: {'adapter' => 'aws_ec2'} do
98
+ before :all do
99
+ provision_node_with_ansible do
100
+ enable_host_auto_discovery
101
+ set_host_key_checking false
102
+ ansible_playbook 'src/ansible/demo.yml', ['--sudo']
103
+ end
104
+ end
105
+ ...
106
+ end
107
+ ```
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+ Rake::Task[:build].prerequisites << Rake::Task[:spec]
@@ -0,0 +1,20 @@
1
+ require 'nodespec/verbose_output'
2
+ require 'nodespec/command_execution'
3
+
4
+ module NodeSpec
5
+ module BackendProxy
6
+ class Base
7
+ include CommandExecution
8
+
9
+ [:create_directory, :create_file].each do |m|
10
+ define_method(m) do |*args|
11
+ execute(send("cmd_#{m}", *args))
12
+ end
13
+ end
14
+
15
+ def execute(command)
16
+ raise "You must subclass #{self.class} and implement #execute"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module NodeSpec
2
+ module BackendProxy
3
+ class Cmd
4
+ def execute command
5
+ raise 'Not supported yet'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ require 'open3'
2
+ require 'nodespec/run_options'
3
+ require_relative 'base'
4
+ require_relative 'unixshell_utility'
5
+
6
+ module NodeSpec
7
+ module BackendProxy
8
+ class Exec < Base
9
+ include UnixshellUtility
10
+
11
+ def execute command
12
+ command = run_as_sudo(command) if NodeSpec::RunOptions.run_local_with_sudo?
13
+ execute_within_timeout(command) do
14
+ output, status = Open3.capture2e(command)
15
+ verbose_puts(output)
16
+ status.success?
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'base'
2
+ require_relative 'unixshell_utility'
3
+
4
+ module NodeSpec
5
+ module BackendProxy
6
+ class Ssh < Base
7
+ include UnixshellUtility
8
+
9
+ ROOT_USER = 'root'
10
+
11
+ def initialize(ssh)
12
+ @ssh_session = ssh
13
+ end
14
+
15
+ def execute(command)
16
+ command = run_as_sudo(command) if @ssh_session.options[:user] != ROOT_USER
17
+ execute_within_timeout(command) do
18
+ success = true
19
+ @ssh_session.exec!(command) do |ch, stream, data|
20
+ verbose_puts(data)
21
+ success = stream != :stderr
22
+ end
23
+ success
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ require 'shellwords'
2
+
3
+ module NodeSpec
4
+ module BackendProxy
5
+ module UnixshellUtility
6
+ SUDO_PREFIX = 'sudo'
7
+ TEMP_DIR = '/tmp'
8
+
9
+ def run_as_sudo(cmd)
10
+ "#{SUDO_PREFIX} #{cmd}"
11
+ end
12
+
13
+ def cmd_create_directory(path)
14
+ shellcmd("mkdir -p #{path.shellescape}")
15
+ end
16
+
17
+ def cmd_create_file(path, content)
18
+ shellcmd("cat > #{path.shellescape} << EOF\n#{content.strip.gsub('"', '\"')}\nEOF")
19
+ end
20
+
21
+ def temp_directory
22
+ TEMP_DIR
23
+ end
24
+
25
+ private
26
+
27
+ def shellcmd(cmd)
28
+ %Q[sh -c "#{cmd}"]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ require 'nodespec/command_execution'
2
+ require 'nodespec/run_options'
3
+ require_relative 'base'
4
+
5
+ module NodeSpec
6
+ module BackendProxy
7
+ class WinRM < Base
8
+ def initialize(winrm)
9
+ @winrm_session = winrm
10
+ end
11
+
12
+ def execute command
13
+ @winrm_session.set_timeout(NodeSpec::RunOptions.command_timeout)
14
+ result = @winrm_session.powershell(command)
15
+ stdout, stderr = [:stdout, :stderr].map do |s|
16
+ result[:data].select {|item| item.key? s}.map {|item| item[s]}.join
17
+ end
18
+ [stdout, stderr].each {|s| verbose_puts s}
19
+ result[:exitcode] == 0 and stderr.empty?
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ require 'specinfra/helper'
2
+ %w[exec cmd ssh winrm].each {|f| require_relative "backend_proxy/#{f}"}
3
+
4
+ module NodeSpec
5
+ module Backends
6
+ class SpecInfraCompatibilityError < StandardError; end
7
+
8
+ %w[Exec Ssh Cmd WinRM].each do |name|
9
+ raise SpecInfraCompatibilityError.new("module SpecInfra::Helper::#{name} is not defined!") unless SpecInfra::Helper.const_defined?(name)
10
+ const_set(name, name)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ require 'timeout'
2
+ require_relative 'verbose_output'
3
+ require_relative 'run_options'
4
+
5
+ module NodeSpec
6
+ module CommandExecution
7
+ class CommandExecutionError < StandardError; end
8
+ include VerboseOutput
9
+
10
+ def execute_within_timeout(command, timeout = NodeSpec::RunOptions.command_timeout, &block)
11
+ verbose_puts "\nExecuting command:\n#{command}"
12
+ command_success = Timeout::timeout(timeout, &block)
13
+ raise CommandExecutionError.new 'The command execution failed. Enable verbosity to check the output.' unless command_success
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ require 'nodespec/runtime_gem_loader'
2
+ require_relative 'ssh_communicator'
3
+ require_relative 'winrm_communicator'
4
+
5
+ module NodeSpec
6
+ module CommunicationAdapters
7
+ class AwsEc2
8
+ GEMLOAD_ERROR = 'In order to use any aws adapter you must install the Amazon Web Service gem'
9
+ def self.communicator_for(node_name, os = nil, options = {})
10
+ RuntimeGemLoader.require_or_fail('aws-sdk', GEMLOAD_ERROR) do
11
+ instance_name = options['instance'] || node_name
12
+ ec2_instance = AWS.ec2.instances[instance_name]
13
+
14
+ raise "EC2 Instance #{instance_name} is not reachable" unless ec2_instance.exists? && ec2_instance.status == :running
15
+ if options.has_key?('winrm')
16
+ WinrmCommunicator.new(ec2_instance.public_dns_name, os, options['winrm'])
17
+ else
18
+ SshCommunicator.new(ec2_instance.public_dns_name, os, options['ssh'] || {})
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ require 'nodespec/backends'
2
+
3
+ module NodeSpec
4
+ module CommunicationAdapters
5
+ module LocalBackend
6
+ def backend_proxy
7
+ BackendProxy.const_get(backend).new
8
+ end
9
+
10
+ def backend
11
+ os == 'Windows' ? Backends::Cmd : Backends::Exec
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ require 'nodespec/verbose_output'
2
+ require_relative 'local_backend'
3
+
4
+ module NodeSpec
5
+ module CommunicationAdapters
6
+ class NativeCommunicator
7
+ include LocalBackend
8
+ include VerboseOutput
9
+
10
+ attr_reader :os
11
+
12
+ def initialize(os = nil)
13
+ @os = os
14
+ end
15
+
16
+ def bind_to(configuration)
17
+ if configuration.ssh
18
+ msg = "\nClosing connection to #{configuration.ssh.host}"
19
+ msg << ":#{configuration.ssh.options[:port]}" if configuration.ssh.options[:port]
20
+ verbose_puts msg
21
+ configuration.ssh.close
22
+ configuration.ssh = nil
23
+ verbose_puts "\nRunning on local host..."
24
+ end
25
+
26
+ if configuration.winrm
27
+ configuration.winrm = nil
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ require 'nodespec/backends'
2
+
3
+ module NodeSpec
4
+ module CommunicationAdapters
5
+ module RemoteBackend
6
+ def backend_proxy
7
+ BackendProxy.const_get(backend).new(session)
8
+ end
9
+
10
+ def backend
11
+ os == 'Windows' ? Backends::WinRM : Backends::Ssh
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ require 'net/ssh'
2
+ require_relative 'ssh_communicator'
3
+
4
+ module NodeSpec
5
+ module CommunicationAdapters
6
+ class Ssh
7
+ def self.communicator_for(node_name, os = nil, options = {})
8
+ opts = options.dup
9
+ host = opts.delete('host') || node_name
10
+ SshCommunicator.new(host, os, opts)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,54 @@
1
+ require 'net/ssh'
2
+ require 'nodespec/verbose_output'
3
+ require_relative 'remote_backend'
4
+
5
+ module NodeSpec
6
+ module CommunicationAdapters
7
+ class SshCommunicator
8
+ include VerboseOutput
9
+ include RemoteBackend
10
+ attr_reader :session, :os
11
+
12
+ def initialize(host, os = nil, options = {})
13
+ @host = host
14
+ @os = os
15
+ @ssh_options = Net::SSH.configuration_for(@host)
16
+ @user = options['user'] || @ssh_options[:user]
17
+ %w[port password keys].each do |param|
18
+ @ssh_options[param.to_sym] = options[param] if options[param]
19
+ end
20
+ end
21
+
22
+ def bind_to(configuration)
23
+ configuration.winrm = nil if configuration.winrm
24
+ current_session = configuration.ssh
25
+
26
+ close_ssh_session(current_session) if current_session && (current_session.host != @host || current_session.options[:port] != @ssh_options[:port])
27
+
28
+ if current_session.nil? || current_session.closed?
29
+ current_session = start_new_ssh_session
30
+ configuration.host = @host
31
+ configuration.ssh = current_session
32
+ end
33
+ @session = current_session
34
+ end
35
+
36
+ private
37
+
38
+ def close_ssh_session(session)
39
+ msg = "\nClosing connection to #{session.host}"
40
+ msg << ":#{session.options[:port]}" if session.options[:port]
41
+ verbose_puts msg
42
+ session.close
43
+ end
44
+
45
+ def start_new_ssh_session
46
+ msg = "\nConnecting to #{@host}"
47
+ msg << ":#{@ssh_options[:port]}" if @ssh_options[:port]
48
+ msg << " as #{@user}..."
49
+ verbose_puts msg
50
+ Net::SSH.start(@host, @user, @ssh_options)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+ require 'open3'
2
+ require_relative 'ssh_communicator'
3
+
4
+ module NodeSpec
5
+ module CommunicationAdapters
6
+ class Vagrant
7
+ def self.communicator_for(node_name, os = nil, options = {})
8
+ vm_name = options['vm_name'] || node_name
9
+ fetch_connection_details(vm_name) do |host, opts|
10
+ SshCommunicator.new(host, os, opts)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def self.fetch_connection_details(vm_name)
17
+ cmd = "vagrant --machine-readable ssh-config #{vm_name}"
18
+ output, status = Open3.capture2e(cmd)
19
+ raise parse_error_data(output) unless status.success?
20
+ yield(parse_ssh_config_data(output)) if block_given?
21
+ end
22
+
23
+ def self.parse_ssh_config_data(data)
24
+ /^\s*HostName\s+(?<hostname>.*)$/ =~ data
25
+ /^\s*Port\s+(?<port>\d+)$/ =~ data
26
+ /^\s*User\s+(?<username>.*)$/ =~ data
27
+ /^\s*IdentityFile\s+(?<private_key_path>.*)$/ =~ data
28
+ [hostname, {'port' => port.to_i, 'user' => username, 'keys' => private_key_path}]
29
+ end
30
+
31
+ def self.parse_error_data(data)
32
+ /^.*,error-exit,(?<error>.*)$/ =~ data
33
+ error
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'winrm_communicator'
2
+
3
+ module NodeSpec
4
+ module CommunicationAdapters
5
+ class Winrm
6
+ def self.communicator_for(node_name, os = nil, options = {})
7
+ opts = options.dup
8
+ hostname = opts.delete('host') || node_name
9
+ WinrmCommunicator.new(hostname, os, opts)
10
+ end
11
+ end
12
+ end
13
+ end