################################################################################ # # Author: Zachary Patten <zachary AT jovelabs DOT com> # Copyright: Copyright (c) Zachary Patten # License: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ################################################################################ require "spec_helper" describe ZTK::SSH do let(:ui) { ZTK::UI.new(:stdout => StringIO.new, :stderr => StringIO.new, :stdin => StringIO.new) } subject { ZTK::SSH.new } describe "class" do it "should be an instance of ZTK::SSH" do expect(subject).to be_an_instance_of ZTK::SSH end end [ :direct, :proxy ].each do |connection_type| before(:each) do subject.config do |config| config.ui = ui config.user = ENV["USER"] config.host_name = "127.0.0.1" if connection_type == :proxy config.proxy_user = ENV["USER"] config.proxy_host_name = "127.0.0.1" end end end describe "#console (#{connection_type})" do it "should execute a console" do expect(Kernel).to receive(:exec) subject.console end end describe "#execute (#{connection_type})" do it "should be able to connect to 127.0.0.1 as the current user and execute a command (your key must be in ssh-agent)" do data = %x(hostname).chomp status = subject.exec("hostname") expect(status.exit_code).to be == 0 ui.stdout.rewind expect(ui.stdout.read.chomp).to match(data) expect(subject.close).to be == true end it "should timeout after the period specified" do subject.config.timeout = WAIT_SMALL expect{ subject.exec("sleep 10") }.to raise_error ZTK::SSHError expect(subject.close).to be == true end it "should throw an exception if the exit status is not as expected" do expect{ subject.exec("exit 42") }.to raise_error ZTK::SSHError expect(subject.close).to be == true end it "should return a instance of an OpenStruct object" do result = subject.exec(%{echo "Hello World"}) expect(result).to be_an_instance_of OpenStruct expect(subject.close).to be == true end it "should return the exit code" do data = 64 result = subject.exec(%{exit #{data}}, :exit_code => data) expect(result.exit_code).to be == data expect(subject.close).to be == true end it "should return the output" do data = "Hello World @ #{Time.now.utc}" result = subject.exec(%Q{echo "#{data}"}) expect(result.output).to match(data) expect(subject.close).to be == true end it "should allow us to change the expected exit code" do data = 32 result = subject.exec(%{exit #{data}}, :exit_code => data) expect(result.exit_code).to be == data expect(subject.close).to be == true end it "should allow us to execute a bootstrap script" do data = "Hello World @ #{Time.now.utc}" result = subject.bootstrap(<<-EOBOOTSTRAP) echo "#{data}" >&1 EOBOOTSTRAP expect(result.output.chomp).to match(data) expect(subject.close).to be == true end it "should allow us to write a file" do data = "Hello World @ #{Time.now.utc}" test_filename = File.join("", "tmp", "test_file.txt") subject.file(:target => test_filename) do |f| f.write(data) end result = subject.exec(%{cat #{test_filename}}) expect(result.output.chomp).to match(data) expect(subject.close).to be == true end end #execute describe "#ui (#{connection_type})" do describe "#stdout (#{connection_type})" do [true, false].each do |request_pty| it "should capture STDOUT #{request_pty ? "with" : "without"} PTY and send it to the STDOUT pipe" do subject.config.request_pty = request_pty data = "Hello World @ #{Time.now.utc}" subject.exec(%{echo "#{data}" >&1}) ui.stdout.rewind expect(ui.stdout.read).to match(data) ui.stderr.rewind expect(ui.stderr.read).to be_empty ui.stdin.rewind expect(ui.stdin.read).to be_empty expect(subject.close).to be == true end end end #stdout describe "#stderr (#{connection_type})" do [true, false].each do |request_pty| it "should capture STDERR #{request_pty ? "with" : "without"} PTY and send it to the #{request_pty ? "STDOUT" : "STDERR"} pipe" do subject.config.request_pty = request_pty data = "Hello World @ #{Time.now.utc}" subject.exec(%{echo "#{data}" >&2}) ui.stdout.rewind expect(ui.stdout.read).to (request_pty ? match(data) : be_empty) ui.stderr.rewind expect(ui.stderr.read).to (request_pty ? be_empty : match(data)) ui.stdin.rewind expect(ui.stdin.read).to be_empty expect(subject.close).to be == true end end end #stderr end #ui describe "#upload (#{connection_type})" do [true, false].each do |use_scp| it "should be able to upload a file to 127.0.0.1 as the current user using #{use_scp ? 'scp' : 'sftp'} (your key must be in ssh-agent)" do data = "Hello World @ #{Time.now.utc}" remote_temp = Tempfile.new('remote') remote_file = File.join(ZTK::Locator.root, "tmp", File.basename(remote_temp.path.dup)) remote_temp.close File.exists?(remote_file) && File.delete(remote_file) local_temp = Tempfile.new('local') local_file = File.join(ZTK::Locator.root, "tmp", File.basename(local_temp.path.dup)) local_temp.close if RUBY_VERSION < "1.9.3" File.open(local_file, 'w') do |file| file.puts(data) end else IO.write(local_file, data) end expect(File.exists?(remote_file)).to be false subject.upload(local_file, remote_file, :use_scp => use_scp) expect(File.exists?(remote_file)).to be == true File.exists?(remote_file) && File.delete(remote_file) File.exists?(local_file) && File.delete(local_file) expect(subject.close).to be == true end end end #upload describe "#download (#{connection_type})" do [true, false].each do |use_scp| it "should be able to download a file from 127.0.0.1 as the current user using #{use_scp ? 'scp' : 'sftp'} (your key must be in ssh-agent)" do data = "Hello World @ #{Time.now.utc}" local_temp = Tempfile.new('local') local_file = File.join(ZTK::Locator.root, "tmp", File.basename(local_temp.path.dup)) local_temp.close File.exists?(local_file) && File.delete(local_file) remote_temp = Tempfile.new('remote') remote_file = File.join(ZTK::Locator.root, "tmp", File.basename(remote_temp.path.dup)) remote_temp.close if RUBY_VERSION < "1.9.3" File.open(remote_file, 'w') do |file| file.puts(data) end else IO.write(remote_file, data) end expect(File.exists?(local_file)).to be false subject.download(remote_file, local_file, :use_scp => use_scp) expect(File.exists?(local_file)).to be == true File.exists?(local_file) && File.delete(local_file) File.exists?(remote_file) && File.delete(remote_file) expect(subject.close).to be == true end end end #download end end