nodespec 0.1.9 → 0.1.10

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.
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