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