From 38d749dc1f2bcec0a5d064fad91ce4c98df249ba Mon Sep 17 00:00:00 2001 From: Charles Strahan Date: Wed, 14 May 2014 22:22:48 -0400 Subject: [PATCH] Add NixOS guest support --- plugins/guests/nixos/cap/change_host_name.rb | 41 ++++++ .../guests/nixos/cap/configure_networks.rb | 129 ++++++++++++++++++ plugins/guests/nixos/guest.rb | 11 ++ plugins/guests/nixos/plugin.rb | 25 ++++ templates/guests/nixos/hostname.erb | 6 + templates/guests/nixos/network.erb | 16 +++ 6 files changed, 228 insertions(+) create mode 100644 plugins/guests/nixos/cap/change_host_name.rb create mode 100644 plugins/guests/nixos/cap/configure_networks.rb create mode 100644 plugins/guests/nixos/guest.rb create mode 100644 plugins/guests/nixos/plugin.rb create mode 100644 templates/guests/nixos/hostname.erb create mode 100644 templates/guests/nixos/network.erb diff --git a/plugins/guests/nixos/cap/change_host_name.rb b/plugins/guests/nixos/cap/change_host_name.rb new file mode 100644 index 000000000..0bfed4a2a --- /dev/null +++ b/plugins/guests/nixos/cap/change_host_name.rb @@ -0,0 +1,41 @@ +require 'tempfile' + +require "vagrant/util/template_renderer" + +module VagrantPlugins + module GuestNixos + module Cap + class ChangeHostName + include Vagrant::Util + + def self.change_host_name(machine, name) + # upload the config file + hostname_module = TemplateRenderer.render("guests/nixos/hostname", :name => name) + upload(machine, hostname_module, "/etc/nixos/vagrant-hostname.nix") + end + + # 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 + machine.communicate.sudo("mv #{remote_temp} #{remote_path}") + end + + # Create a temp file. + def self.mktemp(machine) + path = nil + + machine.communicate.execute("mktemp --suffix -vagrant-upload") do |type, result| + path = result.chomp if type == :stdout + end + path + end + end + end + end +end diff --git a/plugins/guests/nixos/cap/configure_networks.rb b/plugins/guests/nixos/cap/configure_networks.rb new file mode 100644 index 000000000..3649d702b --- /dev/null +++ b/plugins/guests/nixos/cap/configure_networks.rb @@ -0,0 +1,129 @@ +require 'tempfile' +require 'ipaddr' + +require "vagrant/util/template_renderer" + +module VagrantPlugins + module GuestNixos + module Cap + class ConfigureNetworks + include Vagrant::Util + + def self.configure_networks(machine, networks) + # set the prefix length. + networks.each do |network| + network[:prefix_length] = (network[:netmask] && netmask_to_cidr(network[:netmask])) + end + + # set the device names. + assign_device_names(machine, networks) + + # upload the config file + network_module = TemplateRenderer.render("guests/nixos/network", :networks => networks) + upload(machine, network_module, "/etc/nixos/vagrant-network.nix") + end + + # Set :device on each network. + # Attempts to use biosdevname when available to detect interface names, + # and falls back to ifconfig otherwise. + def self.assign_device_names(machine, networks) + if machine.communicate.test("command -v biosdevname") + # use biosdevname to get info about the interfaces + interfaces = get_interfaces(machine) + if machine.provider.capability?(:nic_mac_addresses) + # find device name by MAC lookup. + mac_addresses = machine.provider.capability(:nic_mac_addresses) + networks.each do |network| + mac_address = mac_addresses[network[:interface]+1] + interface = interfaces.detect {|nic| nic[:mac_address].gsub(":","") == mac_address} if mac_address + network[:device] = interface[:kernel] if interface + end + else + # assume interface numbers correspond to (ethN+1). + networks.each do |network| + interface = interfaces.detect {|nic| nic[:ethn] == network[:interface]} + network[:device] = interface[:kernel] if interface + end + end + else + # assume interface numbers correspond to the order of interfaces. + interfaces = get_interface_names(machine) + networks.each do |network| + network[:device] = interfaces[network[:interface]] + end + end + end + + def self.get_interface_names(machine) + output = nil + machine.communicate.execute("ifconfig -a") do |type, result| + output = result.chomp if type == :stdout + end + names = output.scan(/^[^:\s]+/).reject {|name| name =~ /^lo/ } + names + end + + # 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 + machine.communicate.sudo("mv #{remote_temp} #{remote_path}") + end + + # Create a temp file. + def self.mktemp(machine) + path = nil + + machine.communicate.execute("mktemp --suffix -vagrant-upload") do |type, result| + path = result.chomp if type == :stdout + end + path + end + + # using biosdevname, get all interfaces as a list of hashes, where: + # :kernel - the kernel's name for the device, + # :ethn - the calculated ethN-style name converted to integer. + # :mac_address - the permanent mac address. ethN-style name converted to integer. + def self.get_interfaces(machine) + interfaces = [] + + # get all adapters, as named by the kernel + output = nil + machine.communicate.sudo("biosdevname -d") do |type, result| + output = result if type == :stdout + end + kernel_if_names = output.scan(/Kernel name: ([^\n]+)/).flatten + mac_addresses = output.scan(/Permanent MAC: ([^\n]+)/).flatten + + # get ethN-style names + ethns = [] + kernel_if_names.each do |name| + machine.communicate.sudo("biosdevname --policy=all_ethN -i #{name}") do |type, result| + ethns << result.gsub(/[^\d]/,'').to_i if type == :stdout + end + end + + # populate the interface list + kernel_if_names.each_index do |i| + interfaces << { + :kernel => kernel_if_names[i], + :ethn => ethns[i], + :mac_address => mac_addresses[i] + } + end + + interfaces + end + + def self.netmask_to_cidr(mask) + IPAddr.new(mask).to_i.to_s(2).count("1") + end + end + end + end +end diff --git a/plugins/guests/nixos/guest.rb b/plugins/guests/nixos/guest.rb new file mode 100644 index 000000000..5a73b6004 --- /dev/null +++ b/plugins/guests/nixos/guest.rb @@ -0,0 +1,11 @@ +require "vagrant" + +module VagrantPlugins + module GuestNixos + class Guest < Vagrant.plugin("2", :guest) + def detect?(machine) + machine.communicate.test("test -f /run/current-system/nixos-version") + end + end + end +end diff --git a/plugins/guests/nixos/plugin.rb b/plugins/guests/nixos/plugin.rb new file mode 100644 index 000000000..edba634cb --- /dev/null +++ b/plugins/guests/nixos/plugin.rb @@ -0,0 +1,25 @@ +require "vagrant" + +module VagrantPlugins + module GuestNixos + class Plugin < Vagrant.plugin("2") + name "NixOS guest" + description "NixOS guest support." + + guest("nixos", "linux") do + require File.expand_path("../guest", __FILE__) + Guest + end + + guest_capability("nixos", "configure_networks") do + require_relative "cap/configure_networks" + Cap::ConfigureNetworks + end + + guest_capability("nixos", "change_host_name") do + require_relative "cap/change_host_name" + Cap::ChangeHostName + end + end + end +end diff --git a/templates/guests/nixos/hostname.erb b/templates/guests/nixos/hostname.erb new file mode 100644 index 000000000..e86db5286 --- /dev/null +++ b/templates/guests/nixos/hostname.erb @@ -0,0 +1,6 @@ +{ config, pkgs, ... }: +{ + <% if name %> + networking.hostName = "<%= name %>"; + <% end %> +} diff --git a/templates/guests/nixos/network.erb b/templates/guests/nixos/network.erb new file mode 100644 index 000000000..f919fab90 --- /dev/null +++ b/templates/guests/nixos/network.erb @@ -0,0 +1,16 @@ +{ config, pkgs, ... }: +{ + networking.interfaces = [ + <% networks.select {|n| n[:device]}.each do |network| %> + { + name = "<%= network[:device] %>"; + <% if network[:type] == :static %> + ipAddress = "<%= network[:ip] %>"; + <% end %> + <% if network[:prefix_length] %> + prefixLength = <%= network[:prefix_length] %>; + <% end %> + } + <% end %> + ]; +}