Skip to content

Commit

Permalink
Get the SSH config from vagrant
Browse files Browse the repository at this point in the history
This avoids the need to parse the Vagrantfile for the IP. That also
means it can move from static IPs to dynamic IPs.
  • Loading branch information
ekohl committed Mar 29, 2021
1 parent 242646d commit 5cd6320
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 118 deletions.
56 changes: 14 additions & 42 deletions lib/beaker/hypervisor/vagrant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ def private_network_generator(host)
end
end

def connection_preference(host)
[:hostname]
end

def shell_provisioner_generator(provisioner_config)
unless provisioner_config['path'].nil? || provisioner_config['path'].empty?
unless provisioner_config['args'].nil?
Expand Down Expand Up @@ -191,56 +195,31 @@ def set_all_ssh_config
def set_ssh_config host, user
return unless Dir.exist?(@vagrant_path)

f = Tempfile.new("#{host.name}")
ssh_config = Dir.chdir(@vagrant_path) do
stdin, stdout, stderr, wait_thr = Open3.popen3(@vagrant_env, 'vagrant', 'ssh-config', host.name)
if not wait_thr.value.success?
stdout, _, status = Open3.capture3(@vagrant_env, 'vagrant', 'ssh-config', host.name)
unless status.success?
raise "Failed to 'vagrant ssh-config' for #{host.name}"
end
stdout.read
end
#replace hostname with ip
ssh_config = ssh_config.gsub(/Host #{host.name}/, "Host #{host['ip']}") unless not host['ip']

#set the user
ssh_config = ssh_config.gsub(/User vagrant/, "User #{user}")
Tempfile.create do |f|
f.write(stdout)
f.flush

if @options[:forward_ssh_agent] == true
ssh_config = ssh_config.gsub(/IdentitiesOnly yes/, "IdentitiesOnly no")
Net::SSH::Config.for(host.name, [f.path])
end
end

f.write(ssh_config)
f.rewind
ssh_config[:user] = user
ssh_config[:keys_only] = false if @options[:forward_ssh_agent] == true

host[:vagrant_ssh_config] = f.path
host['ssh'] = host['ssh'].merge(Net::SSH.configuration_for(host['ip'], f.path))
host['ssh'] = host['ssh'].merge(ssh_config)
host['user'] = user
@temp_files << f
end

def get_ip_from_vagrant_file(hostname)
ip = ''
if File.file?(@vagrant_file) #we should have a vagrant file available to us for reading
f = File.read(@vagrant_file)
m = /'#{hostname}'.*?ip:\s*('|")\s*([^'"]+)('|")/m.match(f)
if m
ip = m[2]
@logger.debug("Determined existing vagrant box #{hostname} ip to be: #{ip} ")
else
ip = nil
@logger.debug("Unable to determine ip for vagrant box #{hostname}")
end
else
raise("No vagrant file found (should be located at #{@vagrant_file})")
end
ip
end

def initialize(vagrant_hosts, options)
require 'tempfile'
@options = options
@logger = options[:logger]
@temp_files = []
@hosts = vagrant_hosts
@vagrant_path = File.expand_path(File.join(File.basename(__FILE__), '..', '.vagrant', 'beaker_vagrant_files', 'beaker_' + File.basename(options[:hosts_file])))
@vagrant_file = File.expand_path(File.join(@vagrant_path, "Vagrantfile"))
Expand All @@ -253,10 +232,6 @@ def configure(opts = {})
raise "Beaker is configured with provision = false but no vagrant file was found at #{@vagrant_file}. You need to enable provision"
end

@hosts.each do |host|
host[:ip] = get_ip_from_vagrant_file(host.name)
end

set_all_ssh_config
end
super
Expand Down Expand Up @@ -294,9 +269,6 @@ def provision(provider = nil)

def cleanup
@logger.debug "removing temporary ssh-config files per-vagrant box"
@temp_files.each do |f|
f.close()
end
@logger.notify "Destroying vagrant boxes"
vagrant_cmd("destroy --force")
FileUtils.rm_rf(@vagrant_path)
Expand Down
114 changes: 38 additions & 76 deletions spec/beaker/hypervisor/vagrant_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -451,59 +451,57 @@ module Beaker
end

describe "set_ssh_config" do
let( :out ) { double( 'stdout' ) }
let( :out ) do
<<-CONFIG
Host #{name}
HostName 127.0.0.1
User vagrant
Port 2222
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile /home/root/.vagrant.d/insecure_private_key
IdentitiesOnly yes
CONFIG
end
let( :host ) { @hosts[0] }
let( :name ) { host.name }
let( :file ) { double( 'file' ) }
let( :override_options ) { {} }

before :each do
allow( Dir ).to receive( :chdir ).and_yield()
wait_thr = OpenStruct.new
state = double( 'state' )
allow( state ).to receive( :success? ).and_return( true )
wait_thr.value = state

allow( Open3 ).to receive( :popen3 ).with( {"RUBYLIB"=>"", "RUBYOPT"=>""}, 'vagrant', 'ssh-config', name ).and_return( [ "", out, "", wait_thr ])
# FakeFS is just broken with Tempfile
FakeFS.deactivate!
Dir.mktmpdir do |dir|
vagrant.instance_variable_get(:@options).merge!(override_options)
vagrant.instance_variable_set(:@vagrant_path, dir)
state = double( 'state' )
allow( state ).to receive( :success? ).and_return( true )
allow( Open3 ).to receive( :capture3 ).with( {"RUBYLIB"=>"", "RUBYOPT"=>""}, 'vagrant', 'ssh-config', name ).and_return( [ out, "", state ])

allow( file ).to receive( :path ).and_return( '/path/sshconfig' )
allow( file ).to receive( :rewind ).and_return( true )
vagrant.set_ssh_config( host, 'root' )
end
end

allow( out ).to receive( :read ).and_return("Host #{name}
HostName 127.0.0.1
User vagrant
Port 2222
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile /home/root/.vagrant.d/insecure_private_key
IdentitiesOnly yes")
it 'sets the user to root' do
expect(host['user']).to be === 'root'
end

it "can generate a ssh-config file" do
expect( Tempfile ).to receive( :new ).with( "#{host.name}").and_return( file )
expect( Dir ).to receive( :exist? ).with( '/.vagrant/beaker_vagrant_files/beaker_sample.cfg' ).and_return( true )
expect( file ).to receive( :write ).with("Host ip.address.for.#{name}\n HostName 127.0.0.1\n User root\n Port 2222\n UserKnownHostsFile /dev/null\n StrictHostKeyChecking no\n PasswordAuthentication no\n IdentityFile /home/root/.vagrant.d/insecure_private_key\n IdentitiesOnly no")
it 'sets the ssh user to root' do
expect(host['ssh']['user']).to be === 'root'
end

vagrant.set_ssh_config( host, 'root' )
expect( host[:vagrant_ssh_config] ).to be === '/path/sshconfig'
expect( host['ssh'][:config]).to be === false
expect( host['user']).to be === 'root'
# This is because forward_ssh_agent is true by default
it 'sets IdentitiesOnly to no' do
expect(host['ssh'][:keys_only]).to be === false
end

context "when :forward_ssh_agent is false" do
it "should not change IdentitiesOnly to no" do
options = vagrant.instance_variable_get( :@options )
options['forward_ssh_agent'] = false
options = vagrant.instance_variable_set( :@options, options )

expect( Tempfile ).to receive( :new ).with( "#{host.name}").and_return( file )
expect( Dir ).to receive( :exist? ).with( '/.vagrant/beaker_vagrant_files/beaker_sample.cfg' ).and_return( true )
expect( file ).to receive( :write ).with("Host ip.address.for.#{name}\n HostName 127.0.0.1\n User root\n Port 2222\n UserKnownHostsFile /dev/null\n StrictHostKeyChecking no\n PasswordAuthentication no\n IdentityFile /home/root/.vagrant.d/insecure_private_key\n IdentitiesOnly yes")
let(:override_options) do
{forward_ssh_agent: false}
end

vagrant.set_ssh_config( host, 'root' )
expect( host[:vagrant_ssh_config] ).to be === '/path/sshconfig'
expect( host['ssh'][:config]).to be === false
expect( host['user']).to be === 'root'
it "should keep IdentitiesOnly to yes" do
expect( host['ssh'][:keys_only]).to be === true
end
end
end
Expand All @@ -517,17 +515,6 @@ module Beaker
end
end

it 'calls #get_ip_from_vagrant_file' do
vagrant.make_vfile(@hosts)

@hosts.each do |host|
allow(vagrant).to receive(:set_ssh_config).with(host, anything)
expect(vagrant).to receive(:get_ip_from_vagrant_file).with(host.name)
end

vagrant.configure
end

it 'calls #set_all_ssh_config' do
vagrant.make_vfile(@hosts)
expect(vagrant).to receive(:set_all_ssh_config)
Expand Down Expand Up @@ -571,31 +558,6 @@ module Beaker
end
end

describe "get_ip_from_vagrant_file" do
before :each do
allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )
vagrant.make_vfile( @hosts )
end

it "can find the correct ip for the provided hostname" do
@hosts.each do |host|
expect( vagrant.get_ip_from_vagrant_file(host.name) ).to be === host[:ip]
end

end

it "returns nil if it is unable to find an ip" do
expect( vagrant.get_ip_from_vagrant_file("unknown") ).to be_nil
end

it "raises an error if no Vagrantfile is present" do
File.delete( vagrant.instance_variable_get( :@vagrant_file ) )
@hosts.each do |host|
expect{ vagrant.get_ip_from_vagrant_file(host.name) }.to raise_error RuntimeError, /No vagrant file found/
end
end
end

describe "provisioning and cleanup" do

before :each do
Expand Down

0 comments on commit 5cd6320

Please sign in to comment.