All Files
(92.47%
covered at
3.83
hits/line)
7 files in total.
186 relevant lines.
172 lines covered and
14 lines missed
-
1
module Chake
-
-
1
class Backend < Struct.new(:node)
-
-
1
class CommandFailed < Exception
-
end
-
-
1
def scp
-
['scp']
-
end
-
-
1
def scp_dest
-
''
-
end
-
-
1
def rsync
-
['rsync']
-
end
-
-
1
def rsync_dest
-
1
node.path + '/'
-
end
-
-
1
def run(cmd)
-
2
printf "%#{Node.max_node_name_length}s: $ %s\n", node.hostname, cmd
-
2
io = IO.popen(command_runner + ['/bin/sh'], mode='w+', eer: [:child, :out])
-
2
io.write(cmd)
-
2
io.close_write
-
2
io.each_line do |line|
-
4
printf "%#{Node.max_node_name_length}s: %s\n", node.hostname, line.strip
-
end
-
2
io.close
-
2
if $?
-
status = $?.exitstatus
-
if status != 0
-
raise CommandFailed.new([node.hostname, 'FAILED with exit status %d' % status].join(': '))
-
end
-
end
-
end
-
-
1
def run_shell
-
system(*shell_command)
-
end
-
-
1
def run_as_root(cmd)
-
4
if node.remote_username == 'root'
-
2
run(cmd)
-
else
-
2
run('sudo ' + cmd)
-
end
-
end
-
-
1
def to_s
-
1
self.class.backend_name
-
end
-
-
1
def skip?
-
false
-
end
-
-
1
def self.backend_name
-
10
name.split("::").last.downcase
-
end
-
-
1
def self.inherited(subclass)
-
2
@backends ||= []
-
2
@backends << subclass
-
end
-
-
1
def self.get(name)
-
15
backend = @backends.find { |b| b.backend_name == name }
-
6
backend || raise(ArgumentError.new("Invalid backend name: #{name}"))
-
end
-
-
end
-
-
end
-
-
1
require 'chake/backend/ssh'
-
1
require 'chake/backend/local'
-
1
require 'socket'
-
-
1
module Chake
-
-
1
class Backend
-
-
1
class Local < Backend
-
-
1
def command_runner
-
3
['sh', '-c']
-
end
-
-
1
def shell_command
-
ENV.fetch('SHELL', Etc.getpwuid.shell)
-
end
-
-
1
def skip?
-
1
node.hostname != Socket.gethostname
-
end
-
-
end
-
-
end
-
-
end
-
1
module Chake
-
-
1
class Backend
-
-
1
class Ssh < Backend
-
-
1
def scp
-
1
['scp', ssh_config, scp_options].flatten.compact
-
end
-
-
1
def scp_dest
-
ssh_target + ':'
-
end
-
-
1
def rsync
-
[ssh_prefix, 'rsync', rsync_ssh].flatten.compact
-
end
-
-
1
def rsync_dest
-
1
[ssh_target, node.path + '/'].join(':')
-
end
-
-
1
def command_runner
-
4
[ssh_prefix, 'ssh', ssh_config, ssh_options, ssh_target].flatten.compact
-
end
-
-
1
def shell_command
-
command_runner
-
end
-
-
1
private
-
-
1
def rsync_ssh
-
1
@rsync_ssh ||=
-
begin
-
1
ssh_command = 'ssh'
-
1
if File.exist?(ssh_config_file)
-
ssh_command += ' -F ' + ssh_config_file
-
end
-
1
if node.port
-
1
ssh_command += ' -p ' + node.port.to_s
-
end
-
1
if ssh_command == 'ssh'
-
[]
-
else
-
1
['-e', ssh_command]
-
end
-
end
-
end
-
-
1
def ssh_config
-
5
File.exist?(ssh_config_file) && ['-F', ssh_config_file] || []
-
end
-
-
1
def ssh_config_file
-
6
@ssh_config_file ||= ENV.fetch('CHAKE_SSH_CONFIG', '.ssh_config')
-
end
-
-
1
def ssh_prefix
-
4
@ssh_prefix ||= ENV.fetch('CHAKE_SSH_PREFIX', '').split
-
end
-
-
1
def ssh_target
-
5
[node.remote_username, node.hostname].compact.join('@')
-
end
-
-
1
def ssh_options
-
4
node.port && ['-p', node.port.to_s] || []
-
end
-
-
1
def scp_options
-
1
node.port && ['-P', node.port.to_s] || []
-
end
-
-
end
-
-
end
-
-
end
-
1
require 'uri'
-
1
require 'etc'
-
1
require 'forwardable'
-
-
1
require 'chake/backend'
-
-
1
module Chake
-
-
1
class Node
-
-
1
extend Forwardable
-
-
1
attr_reader :hostname
-
1
attr_reader :port
-
1
attr_reader :username
-
1
attr_reader :remote_username
-
1
attr_reader :path
-
1
attr_reader :data
-
-
1
def self.max_node_name_length
-
36
@max_node_name_length ||= 0
-
end
-
1
def self.max_node_name_length=(value)
-
3
@max_node_name_length = value
-
end
-
-
1
def initialize(hostname, data = {})
-
30
uri = URI.parse(hostname)
-
30
if !uri.host && ((!uri.scheme && uri.path) || (uri.scheme && uri.opaque))
-
11
uri = URI.parse("ssh://#{hostname}")
-
end
-
30
if uri.path && uri.path.empty?
-
18
uri.path = nil
-
end
-
-
30
@backend_name = uri.scheme
-
-
30
@hostname = uri.host
-
30
@port = uri.port
-
30
@username = uri.user || Etc.getpwuid.name
-
30
@remote_username = uri.user
-
30
@path = uri.path || "/var/tmp/chef.#{username}"
-
30
@data = data
-
-
30
if @hostname.length > self.class.max_node_name_length
-
3
self.class.max_node_name_length = @hostname.length
-
end
-
end
-
-
1
def backend
-
6
@backend ||= Chake::Backend.get(@backend_name).new(self)
-
end
-
-
1
def_delegators :backend, :run, :run_as_root, :run_shell, :rsync, :rsync_dest, :scp, :scp_dest, :skip?
-
-
end
-
-
end
-
-
1
require 'spec_helper'
-
-
1
describe Chake::Backend::Ssh do
-
-
1
include_examples "Chake::Backend", Chake::Backend::Ssh
-
-
7
let(:node) { Chake::Node.new('ssh://myuser@myhost/srv/chef') }
-
-
2
it('runs commands with ssh') { expect(backend.command_runner).to eq(['ssh', 'myuser@myhost']) }
-
-
2
it('rsyncs over ssh') { expect(backend.rsync_dest).to eq('myuser@myhost:/srv/chef/') }
-
-
1
it 'uses no remote username if none was passed' do
-
1
node = Chake::Node.new('theserver')
-
1
expect(node.username).to eq(Etc.getpwuid.name)
-
1
expect(node.remote_username).to be_nil
-
end
-
-
1
it 'uses username is passwd' do
-
1
expect(node.username).to eq('myuser')
-
1
expect(node.remote_username).to eq('myuser')
-
end
-
-
1
context 'with a custom port' do
-
4
let(:node) { Chake::Node.new('ssh://myhost:2222') }
-
1
it 'uses port with ssh' do
-
1
expect(backend.command_runner).to eq(['ssh', '-p', '2222', 'myhost'])
-
end
-
1
it 'uses port with scp' do
-
1
expect(backend.scp).to eq(['scp', '-P', '2222'])
-
end
-
1
it 'uses port with rsync' do
-
1
expect(backend.send(:rsync_ssh)).to eq(['-e', 'ssh -p 2222'])
-
end
-
end
-
-
end
-
1
require 'chake/backend'
-
-
1
require 'chake/node'
-
-
1
describe Chake::Node do
-
-
1
before do
-
14
ent = double
-
14
allow(ent).to receive(:name).and_return('jonhdoe')
-
14
allow(Etc).to receive(:getpwuid).and_return(ent)
-
end
-
-
8
let(:simple) { Chake::Node.new('hostname') }
-
2
it('has a name') { expect(simple.hostname).to eq('hostname') }
-
2
it('uses ssh by default') { expect(simple.backend).to be_an_instance_of(Chake::Backend::Ssh) }
-
1
it('user current username by default') {
-
1
expect(simple.username).to eq('jonhdoe')
-
}
-
1
it('writes to /var/tmp/chef.$username') {
-
1
expect(simple.path).to eq('/var/tmp/chef.jonhdoe')
-
}
-
-
3
let(:with_username) { Chake::Node.new('username@hostname') }
-
2
it('accepts username') { expect(with_username.username).to eq('username') }
-
2
it('uses ssh') { expect(with_username.backend).to be_an_instance_of(Chake::Backend::Ssh) }
-
-
2
let(:with_backend) { Chake::Node.new('local://hostname')}
-
2
it('accepts backend as URI scheme') { expect(with_backend.backend).to be_an_instance_of(Chake::Backend::Local) }
-
-
1
it('wont accept any backend') do
-
2
expect { Chake::Node.new('foobar://bazqux').backend }.to raise_error(ArgumentError)
-
end
-
-
2
let(:with_data) { Chake::Node.new('local://localhost', 'run_list' => ['recipe[common]']) }
-
1
it('takes data') do
-
1
expect(with_data.data).to be_a(Hash)
-
end
-
-
2
let(:with_port) { Chake::Node.new('ssh://foo.bar.com:2222') }
-
1
it('accepts a port specification') do
-
1
expect(with_port.port).to eq(2222)
-
end
-
-
2
let(:with_port_but_no_scheme) { Chake::Node.new('foo.bar.com:2222') }
-
1
it('accepts a port specification without a scheme') do
-
1
expect(with_port_but_no_scheme.port).to eq(2222)
-
1
expect(with_port_but_no_scheme.backend.to_s).to eq('ssh')
-
end
-
-
1
[:run, :run_as_root, :rsync_dest].each do |method|
-
3
it("delegates #{method} to backend") do
-
3
node = simple
-
-
3
backend = double
-
3
args = Object.new
-
3
allow(node).to receive(:backend).and_return(backend)
-
-
3
expect(backend).to receive(method).with(args)
-
3
node.send(method, args)
-
end
-
end
-
-
end