Use Util::Tempfile when configuring networks

This fixes a fairly large tempfile leak. Vagrant uses a template
renderer to write network configuration files locally to disk. Then,
that temporarily file is uploaded to the remote host and moved into
place. Since Vagrant is such a short-lived process, GC never came along
and cleaned up those tempfiles, resulting in many temporary files being
created through regular Vagrant usage.

The Util::Tempfile class uses a block to ensure the temporary file is
deleted when the block finishes. This API required small tweaks to the
usage, but provides more safety to ensure the files are deleted.
This commit is contained in:
Seth Vargo 2016-05-28 23:17:40 -04:00
parent 3d2390fc94
commit 5a4f345363
No known key found for this signature in database
GPG Key ID: 905A90C2949E8787
22 changed files with 339 additions and 161 deletions

View File

@ -5,11 +5,14 @@ require "log4r"
require_relative "helper"
require_relative "shell"
require_relative "command_filter"
require_relative "../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module CommunicatorWinRM
# Provides communication channel for Vagrant commands via WinRM.
class Communicator < Vagrant.plugin("2", :communicator)
include Vagrant::Util
def self.match?(machine)
# This is useless, and will likely be removed in the future (this
# whole method).
@ -202,15 +205,11 @@ module VagrantPlugins
interactive: interactive,
})
guest_script_path = "c:/tmp/vagrant-elevated-shell.ps1"
file = Tempfile.new(["vagrant-elevated-shell", "ps1"])
begin
file.write(script)
file.fsync
file.close
upload(file.path, guest_script_path)
ensure
file.close
file.unlink
Tempfile.create(["vagrant-elevated-shell", "ps1"]) do |f|
f.write(script)
f.fsync
f.close
upload(f.path, guest_script_path)
end
# Convert to double byte unicode string then base64 encode

View File

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
require "tempfile"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestArch
@ -10,23 +9,29 @@ module VagrantPlugins
include Vagrant::Util
def self.configure_networks(machine, networks)
interfaces = Array.new
tempfiles = []
interfaces = []
machine.communicate.sudo("ip -o -0 addr | grep -v LOOPBACK | awk '{print $2}' | sed 's/://'") do |_, result|
interfaces = result.split("\n")
end
networks.each do |network|
networks.each.with_index do |network, i|
network[:device] = interfaces[network[:interface]]
entry = TemplateRenderer.render("guests/arch/network_#{network[:type]}", options: network)
entry = TemplateRenderer.render("guests/arch/network_#{network[:type]}",
options: network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
remote_path = "/tmp/vagrant-network-#{Time.now.to_i}-#{i}"
machine.communicate.upload(temp.path, "/tmp/vagrant_network")
machine.communicate.sudo("mv /tmp/vagrant_network /etc/netctl/#{network[:device]}")
Tempfile.create("arch-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
machine.communicate.upload(f.path, remote_path)
end
machine.communicate.sudo("mv #{remote_path} /etc/netctl/#{network[:device]}")
machine.communicate.sudo("ip link set #{network[:device]} down && netctl restart #{network[:device]} && netctl enable #{network[:device]}")
end
end

View File

@ -1,6 +1,5 @@
require "tempfile"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestCoreOS
@ -54,11 +53,11 @@ module VagrantPlugins
})
end
Tempfile.open("vagrant") do |temp|
temp.binmode
temp.write(entry)
temp.close
comm.upload(temp.path, "/tmp/etcd-cluster.service")
Tempfile.create("coreos-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
comm.upload(f.path, "/tmp/etcd-cluster.service")
end
comm.sudo("mv /tmp/etcd-cluster.service /media/state/units/")

View File

@ -1,7 +1,7 @@
require 'set'
require 'tempfile'
require "set"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestDebian
@ -29,14 +29,14 @@ module VagrantPlugins
entries << entry
end
# Perform the careful dance necessary to reconfigure
# the network interfaces
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entries.join("\n"))
temp.close
comm.upload(temp.path, "/tmp/vagrant-network-entry")
# Perform the careful dance necessary to reconfigure the network
# interfaces.
Tempfile.create("debian-configure-networks") do |f|
f.write(entries.join("\n"))
f.fsync
f.close
comm.upload(f.path, "/tmp/vagrant-network-entry")
end
# Bring down all the interfaces we're reconfiguring. By bringing down
# each specifically, we avoid reconfiguring eth0 (the NAT interface) so

View File

@ -1,8 +1,8 @@
require "set"
require "tempfile"
require "vagrant/util/retryable"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/retryable"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestFedora
@ -96,12 +96,12 @@ module VagrantPlugins
entry = TemplateRenderer.render("guests/fedora/network_#{network[:type]}",
options: network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
machine.communicate.upload(temp.path, "/tmp/vagrant-network-entry_#{interface}")
Tempfile.create("fedora-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
machine.communicate.upload(f.path, "/tmp/vagrant-network-entry_#{interface}")
end
end
# Bring down all the interfaces we're reconfiguring. By bringing down

View File

@ -1,6 +1,5 @@
require "tempfile"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestFreeBSD
@ -29,13 +28,13 @@ module VagrantPlugins
entry = TemplateRenderer.render("guests/freebsd/network_#{network[:type]}",
options: network, ifname: ifname)
# Write the entry to a temporary location
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
Tempfile.create("freebsd-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
machine.communicate.upload(f.path, "/tmp/vagrant-network-entry")
end
machine.communicate.upload(temp.path, "/tmp/vagrant-network-entry")
machine.communicate.sudo("su -m root -c 'cat /tmp/vagrant-network-entry >> /etc/rc.conf'", {shell: "sh"})
machine.communicate.sudo("rm -f /tmp/vagrant-network-entry", {shell: "sh"})

View File

@ -1,6 +1,5 @@
require "tempfile"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestFuntoo
@ -23,12 +22,15 @@ module VagrantPlugins
ifFile = "netif.eth#{network[:interface]}"
entry = TemplateRenderer.render("guests/funtoo/network_#{network[:type]}",
options: network)
# Upload the entry to a temporary location
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
comm.upload(temp.path, "/tmp/vagrant-#{ifFile}")
Tempfile.create("funtoo-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
comm.upload(f.path, "/tmp/vagrant-#{ifFile}")
end
comm.sudo("cp /tmp/vagrant-#{ifFile} /etc/conf.d/#{ifFile}")
comm.sudo("chmod 0644 /etc/conf.d/#{ifFile}")
comm.sudo("ln -fs /etc/init.d/netif.tmpl /etc/init.d/#{ifFile}")

View File

@ -1,6 +1,5 @@
require "tempfile"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestGentoo
@ -21,12 +20,12 @@ module VagrantPlugins
options: network)
# Upload the entry to a temporary location
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
comm.upload(temp.path, "/tmp/vagrant-network-entry")
Tempfile.create("gentoo-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
comm.upload(f.path, "/tmp/vagrant-network-entry")
end
# Configure the interface
comm.sudo("ln -fs /etc/init.d/net.lo /etc/init.d/net.eth#{network[:interface]}")

View File

@ -1,6 +1,5 @@
require "tempfile"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestNetBSD
@ -20,13 +19,13 @@ module VagrantPlugins
entry = TemplateRenderer.render("guests/netbsd/network_#{network[:type]}",
options: network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
Tempfile.create("netbsd-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
machine.communicate.upload(f.path, "/tmp/vagrant-network-entry")
end
# upload it and append it to the new rc.conf file
machine.communicate.upload(temp.path, "/tmp/vagrant-network-entry")
machine.communicate.sudo("cat /tmp/vagrant-network-entry >> #{newrcconf}")
machine.communicate.sudo("rm -f /tmp/vagrant-network-entry")
@ -45,7 +44,6 @@ module VagrantPlugins
# install new rc.conf
machine.communicate.sudo("install -c -o 0 -g 0 -m 644 #{newrcconf} /etc/rc.conf")
end
end
end

View File

@ -1,6 +1,5 @@
require 'tempfile'
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestNixos
@ -16,13 +15,15 @@ module VagrantPlugins
# Upload a file.
def self.upload(machine, content, remote_path)
local_temp = Tempfile.new("vagrant-upload")
local_temp.binmode
local_temp.write(content)
local_temp.close
remote_temp = mktemp(machine)
machine.communicate.upload(local_temp.path, "#{remote_temp}")
local_temp.delete
Tempfile.create("nixos-change-host-name") do |f|
f.write(content)
f.fsync
f.close
machine.communicate.upload(f.path, "#{remote_temp}")
end
machine.communicate.sudo("mv #{remote_temp} #{remote_path}")
end

View File

@ -1,7 +1,7 @@
require 'tempfile'
require 'ipaddr'
require "ipaddr"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestNixos
@ -65,13 +65,15 @@ module VagrantPlugins
# Upload a file.
def self.upload(machine, content, remote_path)
local_temp = Tempfile.new("vagrant-upload")
local_temp.binmode
local_temp.write(content)
local_temp.close
remote_temp = mktemp(machine)
machine.communicate.upload(local_temp.path, "#{remote_temp}")
local_temp.delete
Tempfile.create("nixos-configure-networks") do |f|
f.write(content)
f.fsync
f.close
machine.communicate.upload(f.path, "#{remote_temp}")
end
machine.communicate.sudo("mv #{remote_temp} #{remote_path}")
end

View File

@ -1,6 +1,5 @@
require "tempfile"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestOpenBSD
@ -13,10 +12,12 @@ module VagrantPlugins
entry = TemplateRenderer.render("guests/openbsd/network_#{network[:type]}",
options: network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
Tempfile.create("openbsd-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
machine.communicate.upload(f.path, "/tmp/vagrant-network-entry")
end
# Determine the interface prefix...
command = "ifconfig -a | grep -o ^[0-9a-z]*"
@ -31,7 +32,6 @@ module VagrantPlugins
end
end
machine.communicate.upload(temp.path, "/tmp/vagrant-network-entry")
machine.communicate.sudo("mv /tmp/vagrant-network-entry /etc/hostname.#{ifname}")
# remove old configurations

View File

@ -1,8 +1,8 @@
require "set"
require "tempfile"
require "vagrant/util/retryable"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/retryable"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestRedHat
@ -55,12 +55,12 @@ module VagrantPlugins
entry = TemplateRenderer.render("guests/redhat/network_#{network[:type]}",
options: network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
machine.communicate.upload(temp.path, "/tmp/vagrant-network-entry_#{network[:interface]}")
Tempfile.create("red-hat-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
machine.communicate.upload(f.path, "/tmp/vagrant-network-entry_#{network[:interface]}")
end
end
# Bring down all the interfaces we're reconfiguring. By bringing down

View File

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
require "tempfile"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestSlackware
@ -20,15 +19,16 @@ module VagrantPlugins
entry = TemplateRenderer.render("guests/slackware/network_#{network[:type]}", options: network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
Tempfile.create("slackware-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
machine.communicate.upload(f.path, "/tmp/vagrant_network")
end
machine.communicate.upload(temp.path, "/tmp/vagrant_network")
machine.communicate.sudo("mv /tmp/vagrant_network /etc/rc.d/rc.inet1.conf")
machine.communicate.sudo("/etc/rc.d/rc.inet1")
end
end
end
end
end

View File

@ -1,8 +1,8 @@
require "set"
require "tempfile"
require "vagrant/util/retryable"
require "vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/retryable"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestSUSE
@ -33,12 +33,12 @@ module VagrantPlugins
entry = TemplateRenderer.render("guests/suse/network_#{network[:type]}",
options: network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
machine.communicate.upload(temp.path, "/tmp/vagrant-network-entry_#{network[:interface]}")
Tempfile.create("suse-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
machine.communicate.upload(f.path, "/tmp/vagrant-network-entry_#{network[:interface]}")
end
end
# Bring down all the interfaces we're reconfiguring. By bringing down

View File

@ -1,11 +1,11 @@
require 'tempfile'
require_relative "base"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module Ansible
module Provisioner
class Guest < Base
include Vagrant::Util
def initialize(machine, config)
super
@ -103,14 +103,14 @@ module VagrantPlugins
inventory_basedir = File.join(config.tmp_path, "inventory")
inventory_path = File.join(inventory_basedir, "vagrant_ansible_local_inventory")
temp_inventory = Tempfile.new("vagrant_ansible_local_inventory_#{@machine.name}")
temp_inventory.write(inventory_content)
temp_inventory.close
create_and_chown_remote_folder(inventory_basedir)
@machine.communicate.tap do |comm|
comm.sudo("rm -f #{inventory_path}", error_check: false)
comm.upload(temp_inventory.path, inventory_path)
@machine.communicate.sudo("rm -f #{inventory_path}", error_check: false)
Tempfile.create("ansible-local-inventory-#{@machine.name}") do |f|
f.write(inventory_content)
f.fsync
f.close
@machine.communicate.upload(f.path, inventory_path)
end
return inventory_basedir

View File

@ -1,7 +1,7 @@
require "digest/md5"
require "tempfile"
require_relative "base"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module Chef
@ -54,17 +54,15 @@ module VagrantPlugins
# Write the raw recipe contents to a tempfile and upload that to the
# machine.
def upload_recipe
# Write the raw recipe contents to a tempfile
file = Tempfile.new(["vagrant-chef-apply", ".rb"])
file.write(config.recipe)
file.rewind
# Write the raw recipe contents to a tempfile and upload
Tempfile.create(["chef-apply", ".rb"]) do |f|
f.write(config.recipe)
f.fsync
f.close
# Upload the tempfile to the guest
@machine.communicate.upload(file.path, target_recipe_path)
ensure
# Delete our template
file.close
file.unlink
# Upload the tempfile to the guest
@machine.communicate.upload(f.path, target_recipe_path)
end
end
end
end

View File

@ -0,0 +1,37 @@
require_relative "../../../../base"
describe "VagrantPlugins::GuestArch::Cap::ChangeHostName" do
let(:described_class) do
VagrantPlugins::GuestArch::Plugin
.components
.guest_capabilities[:arch]
.get(:change_host_name)
end
let(:machine) { double("machine") }
let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
before do
allow(machine).to receive(:communicate).and_return(communicator)
end
after do
communicator.verify_expectations!
end
describe ".change_host_name" do
let(:hostname) { "example.com" }
it "sets the hostname" do
communicator.stub_command("sudo hostname | grep '#{hostname}'", exit_code: 1)
communicator.expect_command("hostnamectl set-hostname #{hostname}")
described_class.change_host_name(machine, hostname)
end
it "does not change the hostname if already set" do
communicator.stub_command("sudo hostname | grep '#{hostname}'", exit_code: 0)
described_class.change_host_name(machine, hostname)
expect(communicator.received_commands.size).to eq(1)
end
end
end

View File

@ -0,0 +1,48 @@
require_relative "../../../../base"
describe "VagrantPlugins::GuestArch::Cap::ConfigureNetworks" do
let(:described_class) do
VagrantPlugins::GuestArch::Plugin
.components
.guest_capabilities[:arch]
.get(:configure_networks)
end
let(:machine) { double("machine") }
let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
before do
allow(machine).to receive(:communicate).and_return(communicator)
communicator.stub_command("ip -o -0 addr | grep -v LOOPBACK | awk '{print $2}' | sed 's/://'",
stdout: "eth1\neth2")
end
after do
communicator.verify_expectations!
end
describe ".configure_networks" do
let(:network_1) do
{
interface: 0,
type: "dhcp",
}
end
let(:network_2) do
{
interface: 1,
type: "static",
ip: "33.33.33.10",
netmask: "255.255.0.0",
gateway: "33.33.0.1",
}
end
it "creates and starts the networks" do
communicator.expect_command("ip link set eth1 down && netctl restart eth1 && netctl enable eth1")
communicator.expect_command("ip link set eth2 down && netctl restart eth2 && netctl enable eth2")
described_class.configure_networks(machine, [network_1, network_2])
end
end
end

View File

@ -0,0 +1,37 @@
require_relative "../../../../base"
describe "VagrantPlugins::GuestCoreOS::Cap::ChangeHostName" do
let(:described_class) do
VagrantPlugins::GuestCoreOS::Plugin
.components
.guest_capabilities[:coreos]
.get(:change_host_name)
end
let(:machine) { double("machine") }
let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
before do
allow(machine).to receive(:communicate).and_return(communicator)
end
after do
communicator.verify_expectations!
end
describe ".change_host_name" do
let(:hostname) { "example.com" }
it "sets the hostname" do
communicator.stub_command("sudo hostname --fqdn | grep '#{hostname}'", exit_code: 1)
communicator.expect_command("hostname example")
described_class.change_host_name(machine, hostname)
end
it "does not change the hostname if already set" do
communicator.stub_command("sudo hostname --fqdn | grep '#{hostname}'", exit_code: 0)
described_class.change_host_name(machine, hostname)
expect(communicator.received_commands.size).to eq(1)
end
end
end

View File

@ -1,10 +1,14 @@
require File.expand_path("../../../../../base", __FILE__)
require File.expand_path("../../../support/shared/debian_like_host_name_examples", __FILE__)
require_relative "../../../../base"
require_relative "../../support/shared/debian_like_host_name_examples"
describe "VagrantPlugins::GuestDebian::Cap::ChangeHostName" do
let(:described_class) do
VagrantPlugins::GuestDebian::Plugin.components.guest_capabilities[:debian].get(:change_host_name)
VagrantPlugins::GuestDebian::Plugin
.components
.guest_capabilities[:debian]
.get(:change_host_name)
end
let(:machine) { double("machine") }
let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
let(:old_hostname) { 'oldhostname.olddomain.tld' }

View File

@ -0,0 +1,50 @@
require_relative "../../../../base"
describe "VagrantPlugins::GuestDebian::Cap::ConfigureNetworks" do
let(:described_class) do
VagrantPlugins::GuestDebian::Plugin
.components
.guest_capabilities[:debian]
.get(:configure_networks)
end
let(:machine) { double("machine") }
let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
before do
allow(machine).to receive(:communicate).and_return(communicator)
end
after do
communicator.verify_expectations!
end
describe ".configure_networks" do
let(:network_0) do
{
interface: 0,
type: "dhcp",
}
end
let(:network_1) do
{
interface: 1,
type: "static",
ip: "33.33.33.10",
netmask: "255.255.0.0",
gateway: "33.33.0.1",
}
end
it "creates and starts the networks" do
communicator.expect_command("/sbin/ifdown eth0 2> /dev/null || true")
communicator.expect_command("/sbin/ip addr flush dev eth0 2> /dev/null")
communicator.expect_command("/sbin/ifdown eth1 2> /dev/null || true")
communicator.expect_command("/sbin/ip addr flush dev eth1 2> /dev/null")
communicator.expect_command("/sbin/ifup eth0")
communicator.expect_command("/sbin/ifup eth1")
described_class.configure_networks(machine, [network_0, network_1])
end
end
end