From 5cd6320f5cebb8f1143e10faaaa91e918713d963 Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Fri, 21 Aug 2020 15:12:46 +0200 Subject: [PATCH] Get the SSH config from vagrant This avoids the need to parse the Vagrantfile for the IP. That also means it can move from static IPs to dynamic IPs. --- lib/beaker/hypervisor/vagrant.rb | 56 +++--------- spec/beaker/hypervisor/vagrant_spec.rb | 114 +++++++++---------------- 2 files changed, 52 insertions(+), 118 deletions(-) diff --git a/lib/beaker/hypervisor/vagrant.rb b/lib/beaker/hypervisor/vagrant.rb index fef46e0..abe776f 100644 --- a/lib/beaker/hypervisor/vagrant.rb +++ b/lib/beaker/hypervisor/vagrant.rb @@ -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? @@ -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")) @@ -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 @@ -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) diff --git a/spec/beaker/hypervisor/vagrant_spec.rb b/spec/beaker/hypervisor/vagrant_spec.rb index dd253cb..d4c4131 100644 --- a/spec/beaker/hypervisor/vagrant_spec.rb +++ b/spec/beaker/hypervisor/vagrant_spec.rb @@ -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 @@ -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) @@ -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