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
@@ -0,0 +1,65 @@
1
+ require 'nodespec/verbose_output'
2
+ require 'nodespec/runtime_gem_loader'
3
+ require_relative 'remote_backend'
4
+
5
+ module NodeSpec
6
+ module CommunicationAdapters
7
+ class WinrmCommunicator
8
+ include RemoteBackend
9
+ include VerboseOutput
10
+ DEFAULT_PORT = 5985
11
+ DEFAULT_TRANSPORT = :plaintext
12
+ DEFAULT_TRANSPORT_OPTIONS = {disable_sspi: true}
13
+
14
+ attr_reader :session, :os
15
+
16
+ def initialize(hostname, os = nil, options = {})
17
+ @os = os
18
+ @hostname = hostname
19
+ opts = options.dup
20
+ port = opts.delete('port') || DEFAULT_PORT
21
+ @endpoint = "http://#{hostname}:#{port}/wsman"
22
+
23
+ if opts.has_key?('transport')
24
+ @transport = opts.delete('transport').to_sym
25
+ @options = opts
26
+ else
27
+ @transport = DEFAULT_TRANSPORT
28
+ @options = DEFAULT_TRANSPORT_OPTIONS.merge(opts)
29
+ end
30
+ @options = @options.inject({}) {|h,(k,v)| h[k.to_sym] = v; h}
31
+ end
32
+
33
+ def bind_to(configuration)
34
+ if configuration.ssh
35
+ close_ssh_session(configuration.ssh)
36
+ configuration.ssh = nil
37
+ end
38
+
39
+ current_session = configuration.winrm
40
+ if current_session.nil? || current_session.endpoint != @endpoint
41
+ current_session = start_winrm_session
42
+ configuration.winrm = current_session
43
+ configuration.host = @hostname
44
+ end
45
+ @session = current_session
46
+ end
47
+
48
+ private
49
+
50
+ def close_ssh_session(session)
51
+ msg = "\nClosing connection to #{session.host}"
52
+ msg << ":#{session.options[:port]}" if session.options[:port]
53
+ verbose_puts msg
54
+ session.close
55
+ end
56
+
57
+ def start_winrm_session
58
+ RuntimeGemLoader.require_or_fail('winrm') do
59
+ verbose_puts "\nConnecting to #{@endpoint}..."
60
+ WinRM::WinRMWebService.new(@endpoint, @transport, @options)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,22 @@
1
+ require 'nodespec/communication_adapters/native_communicator'
2
+
3
+ module NodeSpec
4
+ module CommunicationAdapters
5
+ def self.get_communicator(node_name, os = nil, adapter_name = nil, adapter_options = {})
6
+ if adapter_name
7
+ require_relative "communication_adapters/#{adapter_name}.rb"
8
+ clazz = adapter_class(adapter_name)
9
+ clazz.communicator_for(node_name, os, adapter_options)
10
+ else
11
+ NativeCommunicator.new(os)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def self.adapter_class(name)
18
+ adapter_classname = name.split('_').map(&:capitalize).join('')
19
+ self.const_get(adapter_classname)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ require 'open3'
2
+ require 'nodespec/run_options'
3
+ require 'nodespec/command_execution'
4
+
5
+ module NodeSpec
6
+ module LocalCommandRunner
7
+ include CommandExecution
8
+
9
+ def run_command command
10
+ execute_within_timeout(command) do
11
+ output, status = Open3.capture2e(command)
12
+ verbose_puts(output)
13
+ status.success?
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,56 @@
1
+ require 'specinfra/helper'
2
+ require 'nodespec/communication_adapters'
3
+ require 'nodespec/communication_adapters/native_communicator'
4
+
5
+ module NodeSpec
6
+ class Node
7
+ class BadNodeNameError < StandardError; end
8
+
9
+ WORKING_DIR = '.nodespec'
10
+ attr_reader :os, :communicator, :name
11
+
12
+ def initialize(node_name, options = nil)
13
+ @name = validate(node_name)
14
+ opts = (options || {}).dup
15
+ @os = opts.delete('os')
16
+ adapter_name = opts.delete('adapter')
17
+ @communicator = CommunicationAdapters.get_communicator(@name, @os, adapter_name, opts)
18
+ end
19
+
20
+ def backend
21
+ @communicator.backend
22
+ end
23
+
24
+ [:create_directory, :create_file].each do |met|
25
+ define_method(met) do |*args|
26
+ path_argument = args.shift
27
+ unless path_argument.start_with?('/')
28
+ backend_proxy.create_directory WORKING_DIR
29
+ path_argument = "#{WORKING_DIR}/#{path_argument}"
30
+ end
31
+ backend_proxy.send(met, path_argument, *args)
32
+ path_argument
33
+ end
34
+ end
35
+
36
+ def create_temp_directory(path)
37
+ path = path[1..-1] if path.start_with?('/')
38
+ create_directory("#{backend_proxy.temp_directory}/#{path}")
39
+ end
40
+
41
+ def execute(command)
42
+ backend_proxy.execute(command)
43
+ end
44
+
45
+ private
46
+
47
+ def backend_proxy
48
+ @backend_proxy ||= @communicator.backend_proxy
49
+ end
50
+
51
+ def validate(name)
52
+ raise BadNodeNameError.new unless name =~ /^[a-zA-Z0-9][a-zA-Z0-9. \-_]+\s*$/
53
+ name.strip.gsub(' ', '-')
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,29 @@
1
+ require 'yaml'
2
+ require 'singleton'
3
+ require_relative 'node'
4
+
5
+ module NodeSpec
6
+ class NodeConfigurations
7
+ include Singleton
8
+ attr_reader :current_settings
9
+
10
+ def initialize
11
+ filename = ENV['NODESPEC_CONFIG'] || 'nodespec_config.yml'
12
+ data = YAML.load_file(filename) if File.exists?(filename)
13
+ @predefined_settings = data || {}
14
+ end
15
+
16
+ def get(node_name, options = nil)
17
+ case options
18
+ when String
19
+ raise "Cannot find nodespec settings '#{options}'" unless @predefined_settings.key?(options)
20
+ opts = @predefined_settings[options]
21
+ when Hash
22
+ opts = options
23
+ else
24
+ opts = {}
25
+ end
26
+ Node.new(node_name, opts)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,96 @@
1
+ require 'shellwords'
2
+ require 'tempfile'
3
+ require 'json'
4
+ require 'erb'
5
+ require 'nodespec/local_command_runner'
6
+
7
+ module NodeSpec
8
+ module Provisioning
9
+ class Ansible
10
+ include LocalCommandRunner
11
+ CUSTOM_CONFIG_FILENAME = 'nodespec_ansible_cfg'
12
+ CUSTOM_INVENTORY_FILENAME = 'nodespec_ansible_hosts'
13
+ AUTO_DISCOVERY_HOST_TEMPLATE = <<-EOS
14
+ <%= "[" + group + "]" if group %>
15
+ <%= @node.name %> ansible_ssh_host=<%= @node.communicator.session.transport.host %> ansible_ssh_port=<%= @node.communicator.session.transport.port %>
16
+ EOS
17
+
18
+ def initialize(node)
19
+ @node = node
20
+ @sudo_enabled = true
21
+ @cmd_prefix_entries = []
22
+ @tmp_files = []
23
+ end
24
+
25
+ def set_config_path(path)
26
+ @cmd_prefix_entries << "ANSIBLE_CONFIG=#{path.shellescape}"
27
+ end
28
+
29
+ def ansible_config(text)
30
+ file = create_temp_file(CUSTOM_CONFIG_FILENAME, text)
31
+ @cmd_prefix_entries << "ANSIBLE_CONFIG=#{file.path.shellescape}"
32
+ end
33
+
34
+ def enable_host_auto_discovery(group = nil)
35
+ file = create_temp_file(CUSTOM_INVENTORY_FILENAME, ERB.new(AUTO_DISCOVERY_HOST_TEMPLATE).result(binding))
36
+ @hostfile_option = "-i #{file.path.shellescape}"
37
+ end
38
+
39
+ def set_hostfile_path(path)
40
+ @hostfile_option = "-i #{path.shellescape}"
41
+ end
42
+
43
+ def set_host_key_checking(enabled)
44
+ @cmd_prefix_entries << "ANSIBLE_HOST_KEY_CHECKING=#{enabled.to_s.capitalize}"
45
+ end
46
+
47
+ def run_as_sudo(enabled = true)
48
+ @sudo_enabled = enabled
49
+ end
50
+
51
+ def set_extra_vars(vars = {})
52
+ @extra_vars_option = "-e '#{JSON.generate(vars)}'"
53
+ end
54
+
55
+ def ansible_playbook(playbook_path, options = [])
56
+ build_and_run("ansible-playbook #{playbook_path.shellescape} -l #{@node.name}", options)
57
+ end
58
+
59
+ def ansible_module(module_name, module_arguments, options = [])
60
+ build_and_run("ansible #{@node.name} -m #{module_name} -a #{module_arguments.shellescape}", options)
61
+ end
62
+
63
+ private
64
+
65
+ def build_and_run(cmd, options = [])
66
+ ssh_session = @node.communicator.session
67
+ key_option = ssh_session.options[:keys].is_a?(Array) ? ssh_session.options[:keys].join(',') : ssh_session.options[:keys]
68
+ cmd = [
69
+ (@cmd_prefix_entries.join(' ') unless @cmd_prefix_entries.empty?),
70
+ cmd,
71
+ @hostfile_option,
72
+ "-u #{ssh_session.options[:user]}",
73
+ "--private-key=#{key_option.shellescape}",
74
+ sudo_option(ssh_session.options[:user]),
75
+ "#{options.join(' ')}",
76
+ @extra_vars_option
77
+ ].compact.join(' ')
78
+
79
+ run_command(cmd)
80
+ @tmp_files.each(&:close!)
81
+ end
82
+
83
+ def sudo_option(user)
84
+ '--sudo' if user != 'root' and @sudo_enabled
85
+ end
86
+
87
+ def create_temp_file(filename, content)
88
+ Tempfile.new(filename).tap do |f|
89
+ f.write(content)
90
+ f.flush
91
+ @tmp_files << f
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,68 @@
1
+ require 'shellwords'
2
+ require 'json'
3
+
4
+ module NodeSpec
5
+ module Provisioning
6
+ class Chef
7
+ CLIENT_CONFIG_FILENAME = 'chef_client.rb'
8
+ ATTRIBUTES_FILENAME = 'chef_client_attributes.json'
9
+ NODES_DIRNAME = 'chef_nodes'
10
+
11
+ def initialize(node)
12
+ @node = node
13
+ @custom_attributes = {}
14
+ @configuration_entries = []
15
+ end
16
+
17
+ def chef_apply_execute(snippet, options = [])
18
+ @node.execute("chef-apply #{options.join(' ')} -e #{snippet.shellescape}")
19
+ end
20
+
21
+ def chef_apply_recipe(recipe_file, options = [])
22
+ @node.execute("chef-apply #{recipe_file.shellescape} #{options.join(' ')}")
23
+ end
24
+
25
+ def set_cookbook_paths(*paths)
26
+ unless paths.empty?
27
+ paths_in_quotes = paths.map {|p| "'#{p}'"}
28
+ @configuration_entries << %Q(cookbook_path [#{paths_in_quotes.join(",")}])
29
+ end
30
+ end
31
+
32
+ def set_attributes(attributes)
33
+ @custom_attributes = attributes
34
+ end
35
+
36
+ def chef_client_config(text)
37
+ @configuration_entries << text
38
+ end
39
+
40
+ def chef_client_runlist(*args)
41
+ run_list_items, options = [], []
42
+ run_list_items << args.take_while {|arg| arg.is_a? String}
43
+ options += args.last if args.last.is_a? Array
44
+ options << configuration_option
45
+ options << attributes_option
46
+ @node.execute("chef-client -z #{options.compact.join(' ')} -o #{run_list_items.join(',').shellescape}")
47
+ end
48
+
49
+ private
50
+
51
+ def configuration_option
52
+ unless @configuration_entries.any? {|c| c =~ /^node_path .+$/}
53
+ nodes_directory = @node.create_temp_directory(NODES_DIRNAME)
54
+ @configuration_entries.unshift("node_path '#{nodes_directory}'")
55
+ end
56
+ config_file = @node.create_file(CLIENT_CONFIG_FILENAME, @configuration_entries.join("\n"))
57
+ "-c #{config_file}"
58
+ end
59
+
60
+ def attributes_option
61
+ unless @custom_attributes.empty?
62
+ attr_file = @node.create_file(ATTRIBUTES_FILENAME, JSON.generate(@custom_attributes))
63
+ "-j #{attr_file}"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,55 @@
1
+ require 'yaml'
2
+ require 'shellwords'
3
+ require 'erb'
4
+
5
+ module NodeSpec
6
+ module Provisioning
7
+ class Puppet
8
+ HIERADATA_DIRNAME = 'puppet_hieradata'
9
+ HIERA_CONFIG_FILENAME = 'puppet_hiera.yaml'
10
+ HIERA_DEFAULT_HIERARCHY = 'common'
11
+ HIERA_CONFIG_TEMPLATE = <<-EOS
12
+ :backends:
13
+ - yaml
14
+ :yaml:
15
+ :datadir: <%= hieradata_dir %>
16
+ :hierarchy:
17
+ - #{HIERA_DEFAULT_HIERARCHY}
18
+ EOS
19
+ def initialize(node)
20
+ @node = node
21
+ end
22
+
23
+ def set_modulepaths(*paths)
24
+ @modulepath_option = "--modulepath #{paths.join(':').shellescape}" unless paths.empty?
25
+ end
26
+
27
+ def set_facts(facts)
28
+ @facts = facts.reduce("") { |fact, pair| "FACTER_#{pair[0]}=#{pair[1].shellescape} #{fact}" }
29
+ end
30
+
31
+ def set_hieradata(values)
32
+ unless values.empty?
33
+ hieradata_dir = @node.create_directory(HIERADATA_DIRNAME)
34
+ @node.create_file("#{HIERADATA_DIRNAME}/#{HIERA_DEFAULT_HIERARCHY}.yaml", YAML.dump(values))
35
+ hiera_config = @node.create_file(HIERA_CONFIG_FILENAME, ERB.new(HIERA_CONFIG_TEMPLATE).result(binding))
36
+ @hiera_option = "--hiera_config #{hiera_config}"
37
+ end
38
+ end
39
+
40
+ def puppet_apply_execute(snippet, options = [])
41
+ @node.execute("#{group_command_options(options)} -e #{snippet.shellescape}")
42
+ end
43
+
44
+ def puppet_apply_manifest(manifest_file, options = [])
45
+ @node.execute("#{group_command_options(options)} #{manifest_file.shellescape}")
46
+ end
47
+
48
+ private
49
+
50
+ def group_command_options(options)
51
+ %Q[#{@facts}puppet apply #{@modulepath_option} #{@hiera_option} #{options.join(' ')}]
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ require 'shellwords'
2
+
3
+ module NodeSpec
4
+ module Provisioning
5
+ class Shellscript
6
+ def initialize(node)
7
+ @node = node
8
+ end
9
+
10
+ def execute_file(path)
11
+ @node.execute(path)
12
+ end
13
+
14
+ def execute_script(script)
15
+ @node.execute("sh -c #{script.shellescape}")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ Dir[File.join(File.dirname(__FILE__), 'provisioning/*.rb')].each {|f| require f}
2
+
3
+ module NodeSpec
4
+ module Provisioning
5
+ self.constants.each do |provisioner_name|
6
+ provisioner_class = self.const_get(provisioner_name)
7
+ define_method("provision_node_with_#{provisioner_name.downcase}".to_sym) do |&block|
8
+ @provisioners ||= {}
9
+ @provisioners[provisioner_name.downcase] = provisioner_class.new(NodeSpec.current_node) unless @provisioners.key?(provisioner_name.downcase)
10
+ @provisioners[provisioner_name.downcase].instance_eval(&block)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module NodeSpec
2
+ module RunOptions
3
+ class << self
4
+ [:verbose, :run_local_with_sudo].each do |attr|
5
+ attr_accessor attr
6
+ alias_method("#{attr}?".to_sym, attr)
7
+ end
8
+ attr_writer :command_timeout
9
+ def command_timeout
10
+ @command_timeout || 600
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module NodeSpec
2
+ module RuntimeGemLoader
3
+ DEFAULT_ERROR_MSG = 'Consider installing the missing gem'
4
+ def self.require_or_fail(gem_name, error_message = nil)
5
+ begin
6
+ require gem_name
7
+ yield if block_given?
8
+ rescue LoadError => e
9
+ err = <<-EOS
10
+ Error: #{e.message}
11
+ #{error_message || DEFAULT_ERROR_MSG}
12
+
13
+ gem install '#{gem_name}'
14
+ EOS
15
+ fail(err)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ require 'rspec'
2
+ module NodeSpec
3
+ module SharedExamplesSupport
4
+ def it_is_node_with_roles *instance_roles
5
+ instance_roles.each {|role| it_behaves_like role}
6
+ end
7
+ end
8
+ end
9
+
10
+ RSpec.configure do |config|
11
+ config.alias_it_behaves_like_to :it_is_node_configured_with, 'is a node configured with:'
12
+ config.extend NodeSpec::SharedExamplesSupport
13
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'run_options'
2
+
3
+ module NodeSpec
4
+ module VerboseOutput
5
+ def verbose_puts(msg)
6
+ puts msg if NodeSpec::RunOptions.verbose?
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module NodeSpec
2
+ VERSION = '0.1.10'
3
+ end
data/nodespec.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ libdir = File.join(File.dirname(__FILE__), 'lib')
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ require 'nodespec/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'nodespec'
8
+ gem.version = NodeSpec::VERSION
9
+ gem.summary = 'RSpec style tests for multiple nodes/server instances with support for provisioning instructions'
10
+ gem.description = 'RSpec style tests for multiple nodes/server instances with support for provisioning instructions'
11
+
12
+ gem.authors = ['Silvio Montanari']
13
+ gem.homepage = 'https://github.com/smontanari/nodespec'
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.test_files = gem.files.grep(%r{^spec/})
16
+ gem.require_paths = ['lib']
17
+
18
+ gem.required_ruby_version = '>= 1.9.3'
19
+
20
+ gem.add_runtime_dependency 'net-ssh'
21
+ gem.add_runtime_dependency 'serverspec'
22
+ gem.add_runtime_dependency 'specinfra', '>= 1.18.4'
23
+ gem.add_development_dependency 'rspec', '~> 3.0'
24
+ gem.add_development_dependency 'aws-sdk'
25
+ gem.add_development_dependency 'winrm'
26
+ gem.add_development_dependency 'bundler'
27
+ gem.add_development_dependency 'rake'
28
+ end
@@ -0,0 +1,29 @@
1
+ require 'nodespec/backend_proxy/base'
2
+
3
+ module NodeSpec
4
+ module BackendProxy
5
+ describe Base do
6
+ describe '#create_file' do
7
+ it 'executes the generated command' do
8
+ allow(subject).to receive(:cmd_create_file).with('test/path', 'test content').and_return('command')
9
+ expect(subject).to receive(:execute).with('command')
10
+
11
+ subject.create_file('test/path', 'test content')
12
+ end
13
+ end
14
+
15
+ describe '#create_directory' do
16
+ it 'executes the generated command' do
17
+ allow(subject).to receive(:cmd_create_directory).with('test/path').and_return('command')
18
+ expect(subject).to receive(:execute).with('command')
19
+
20
+ subject.create_directory('test/path')
21
+ end
22
+ end
23
+
24
+ it 'behaves like a command execution' do
25
+ expect(subject).to respond_to(:execute_within_timeout)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'nodespec/backend_proxy/exec'
3
+
4
+ module NodeSpec
5
+ module BackendProxy
6
+ describe Exec do
7
+ shared_examples 'a command run' do |original_command, actual_command, sudo_option|
8
+ let(:cmd_status) { double('status') }
9
+
10
+ before do
11
+ NodeSpec::RunOptions.run_local_with_sudo = sudo_option
12
+ allow(Open3).to receive(:capture2e).with(actual_command).and_return(['test output', cmd_status])
13
+ allow(subject).to receive(:execute_within_timeout).with(actual_command).and_yield
14
+ end
15
+
16
+
17
+ it 'returns true if the command succeeds' do
18
+ allow(cmd_status).to receive(:success?).and_return(true)
19
+
20
+ expect(subject.execute(original_command)).to be_truthy
21
+ end
22
+
23
+ it 'returns false if the command fails' do
24
+ allow(cmd_status).to receive(:success?).and_return(false)
25
+
26
+ expect(subject.execute(original_command)).to be_falsy
27
+ end
28
+ end
29
+
30
+ it_behaves_like 'a command run', 'test command', 'test command', false
31
+ it_behaves_like 'a command run', 'test command', 'sudo test command', true
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ require 'nodespec/backend_proxy/ssh'
2
+
3
+ module NodeSpec
4
+ module BackendProxy
5
+ describe Ssh do
6
+ let(:ssh_session) { double('ssh session') }
7
+ let(:subject) {Ssh.new(ssh_session)}
8
+
9
+ shared_examples 'an ssh session command run' do |user, original_command, actual_command|
10
+ before do
11
+ allow(ssh_session).to receive(:options).and_return({user: user})
12
+ allow(subject).to receive(:execute_within_timeout).with(actual_command).and_yield
13
+ end
14
+
15
+ it 'returns true if the command succeeds' do
16
+ allow(ssh_session).to receive(:exec!).with(actual_command).and_yield(nil, 'a stream', 'test data')
17
+
18
+ expect(subject.execute(original_command)).to be_truthy
19
+ end
20
+
21
+ it 'returns false if the command fails' do
22
+ allow(ssh_session).to receive(:exec!).with(actual_command).and_yield(nil, :stderr, 'test data')
23
+
24
+ expect(subject.execute(original_command)).to be_falsy
25
+ end
26
+ end
27
+
28
+ it_behaves_like 'an ssh session command run', 'root', 'test command', 'test command'
29
+ it_behaves_like 'an ssh session command run', 'some_user', 'test command', 'sudo test command'
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ require 'nodespec/backend_proxy/unixshell_utility'
2
+
3
+ module NodeSpec
4
+ module BackendProxy
5
+ describe UnixshellUtility do
6
+ let(:subject) {Object.new.extend UnixshellUtility}
7
+
8
+ it 'returns the command as run by sudo' do
9
+ expect(subject.run_as_sudo('command')).to eq 'sudo command'
10
+ end
11
+
12
+ it 'returns the command to create a directory' do
13
+ expect(subject.cmd_create_directory('/path to/dir')).to eq('sh -c "mkdir -p /path\ to/dir"')
14
+ end
15
+
16
+ it 'returns the path to the temp directory' do
17
+ expect(subject.temp_directory).to eq('/tmp')
18
+ end
19
+
20
+ it 'writes the given content to a file' do
21
+ content = <<-eos
22
+ some 'text'
23
+ some "other" text
24
+ eos
25
+ expect(subject.cmd_create_file('/path to/file', content)).to eq %Q[sh -c "cat > /path\\ to/file << EOF\nsome 'text'\nsome \\"other\\" text\nEOF"]
26
+ end
27
+ end
28
+ end
29
+ end