Vagrant - How to use nfs for all synced folders? - ruby

I want to instruct Vagrant (through Vagrantfile) to use nfs for all synced_folder declared.
I guess it will be something like this:
vm.synced_folder.each do ...
... use nfs
end
But I don't know ruby's syntax.
Now experimenting with:
# Use nfs for better performance
config.vm.synced_folder.each do |id, options|
if ! options[:type]
options[:type] = "nfs"
end
end

One thing you can try is to declare all the sync folder you want to make and then loop through that - something like
sharedfolderlist = {
"/folder_vm_1" => "folder_from_host/",
"/folder_vm_1" => "/can_be_full_path_folder_from_host/",
}
sharedfolder.each do |vm, host|
config.vm.synced_folder host, vm, nfs: true
end
Its not brilliant but could do the job.

Related

Ruby Vagrant network configuration duplication trouble, some objects references issue

I will say right away - I'm experienced in Python for example, but Ruby is totally new for me so this question may be not related to Vagrant at all, I don't know, sorry.
I want to create two VMs on my host and created Vagrantfile:
Vagrant.configure("2") do |config|
# Image config
config.vm.box = "ubuntu/bionic64"
config.disksize.size = '40GB'
# Nodes specific configs
config.vm.define "node_1_1" do |node|
node.vm.network "public_network", ip: "192.168.3.11", bridge: "enp4s0", netmask: "255.255.248.0"
node.vm.hostname = "vm-ci-node-1-1"
end
config.vm.define "node_1_2" do |node|
node.vm.network "public_network", ip: "192.168.3.12", bridge: "enp4s0", netmask: "255.255.248.0"
node.vm.hostname = "vm-ci-node-1-2"
end
# Nodes generic configs
config.vm.provider "virtualbox" do |vb|
vb.memory = "2048"
vb.cpus=2
end
end
It works fine.
Then I decided to remove parameters hardcode and optimize it for next case with more than two VMs and for correct work on another machines with another bridge interface names. So I replaced Nodes specific configs section with:
target_interface = nil
for if_addr in Socket.getifaddrs
if if_addr.addr.ipv4? and if_addr.addr.ip_address.include? '192.168'
target_interface = if_addr.name
end
end
hostindex = 8
guestindices = [1, 2]
# Nodes specific configs
for guestindex in guestindices
vm_code = 'node_' + hostindex.to_s() + '_' + guestindex.to_s()
ip = '192.168.3.' + hostindex.to_s() + guestindex.to_s()
hostname = 'vm-ci-node-' + hostindex.to_s() + '-' + guestindex.to_s()
config.vm.define vm_code.dup do |node|
node.vm.network "public_network", ip: ip.dup, bridge: target_interface, netmask: "255.255.248.0"
node.vm.hostname = hostname.dup
end
end
Then I run vagrant up - no errors, but if I try to SSH to these two machines - I get strange behaviour:
node_8_1 IP address is 192.168.3.82, hostname - vm-ci-node-8-2
node_8_2 IP address is 192.168.3.82, hostname - vm-ci-node-8-2
As you see - it is same. Also there are another interface with same IP 10.0.2.15 - and it is trouble too, but it existed on previous version of config too.
I suspected that there are some Ruby references troubles so I used dup (I repeat, I'm totally new to Ruby, sorry). But it does not seem to work.
VM codes are different - node_8_1 and node_8_2, but IP and hostnames are same.
Could anybody please point me where I'm wrong?
I think the for guestindex in guestindices part is the cause of your trouble.
Try using guestindices.each do |i| or shorter (1..2).each do |i| instead.
Vagrant is mentioning this in their documentation for multi-machine provisioning, see https://www.vagrantup.com/docs/vagrantfile/tips.html#loop-over-vm-definitions:
The for i in ... construct in Ruby actually modifies the value of i for each iteration, rather than making a copy. Therefore, when you run this, every node will actually provision with the same [value].

How do I reuse variables in quoted line in a Vagrantfile?

I'm putting together a Vagrantfile that can be used to spin up multiple VMs, it mostly works apart from the bit where I need to tell ansible which playbook to use. This is being imposed on top of an existing structure so there's little scope to change file locations and that.
Here's an extract of the relevant bits from my Vagrantfile:
hosts = [
{ name: 'myhost01', hostname: 'vg-myhost01', ip: '172.172.99.99', memory:'512', cpu: 1, box: 'centos', port_forward: [] },
]
config.vm.provision :ansible do |ansible|
ansible.playbook = ['install/mydir/install_', :name, '.yml']
end
Basically I'm trying to figure out how to get it to end up with a setting like
ansible.playbook = 'install/mydir/install_myhost01.yml'
but I can't seem to get the right syntax to get it to recognise :name as a variable in that context. It either tries to run install_.yml, install_name.yml or most commonly gives the error:
`initialize': no implicit conversion of Symbol into String (TypeError)
Any suggestions?
It's more a question about Ruby syntax. You can use:
ansible.playbook = 'install/mydir/install_' + name + '.yml'
or (thanks to Frédéric Henri)
ansible.playbook = "install/mydir/install_#{name}.yml"
To reference the value from the hash (per OP's own suggestion):
ansible.playbook = "install/mydir/install_#{host[:name]}.yml"

How to multi-provision in kitchen.yml?

I have a Vagrantfile in which I am provisioning different Vm's by looping through a json file. eg.
cluster_config.each do |cluster|
cluster_name = cluster[0] # name of node
nodes_config = (JSON.parse(File.read("test_data_bags/myapp/_default.json")))['clusters'][cluster_name]['nodes']
nodes_config.each do |node|
config.vm.define node_name do |nodeconfig|
processes = node_values['processes']
processes.each do |process|
nodeconfig.vm.provision :chef_solo do |chef|
chef.data_bags_path = 'test_data_bags'
chef.run_list = run_list
chef.roles_path = "roles"
"myapp" => {
"cluster_name" => cluster_name,
"role" => node_role
},
}
end
end
end
end
I would like to do the same within kitchen ie. take an array of attributes and foreach array item - run recipe xyz - this is so I can write some tests using test-kitchen , is this possible?
Thanks
There's a few different workarounds to accomplish this, but they are all definitely workarounds. There is an issue that was opened to discuss support of multiple boxes on test-kitchen, and you can go there to read more about why this probably won't be supported any time soon. TL;DR: it's not really a goal of the project.
Workarounds include:
Chef-provisioning can bootstrap more servers from a single provisioned server/test-suite
Kitchen-nodes provisioner can share data about each server to the other test-suites in your setup
A custom Vagrantfile template for test-kitchen

Vagrant - how to have host platform specific provisioning steps

We've got a diverse dev team, one on Windows, another on Ubuntu and another on OSX. Being windows boy, I setup the first version of the vagrant setup script which works fabulously ;)
However, when running it on the Ubuntu host, the first time it gets to a provision step that calls a bash script, it fails due to permissions.
On windows, this doesn't matter as the samba share automatically has sufficient permissions to run the bash script (which resides within the project hierarchy, so is present in the /vagrant share on the VM), but with ubuntu I need to set the permissions on this file in the provision script before I call it.
This isn't the problem and to be honest I suspect even with the extra "chmod" step it would still work fine under windows, but, is there a way in the vagrant file to flag certain provisioning steps as 'Windows Only', 'Linux Only' or 'Mac Only'?
i.e. in pseduo code, something like.
.
.
if (host == windows) then
config.vm.provision : shell, : inline => "/vagrant/provisioning/only_run_this_on_windows.sh"
else if (host == linux) then
config.vm.provision : shell, : inline => "/vagrant/provisioning/only_run_this_on_linux.sh"
else if (host == osx) then
config.vm.provision : shell, : inline => "/vagrant/provisioning/only_run_this_on_osx.sh"
end if
.
.
Thanks in advance.
Note that Vagrant itself, in the Vagrant::Util::Platform class already implements a more advanced version of the platform checking logic in the answer by BernardoSilva.
So in a Vagrantfile, you can simply use the following:
if Vagrant::Util::Platform.windows? then
myHomeDir = ENV["USERPROFILE"]
else
myHomeDir = "~"
end
Find out current OS inside Vagrantfile.
Add this into your Vagrantfile:
module OS
def OS.windows?
(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
end
def OS.mac?
(/darwin/ =~ RUBY_PLATFORM) != nil
end
def OS.unix?
!OS.windows?
end
def OS.linux?
OS.unix? and not OS.mac?
end
end
Then you can use it as you like.
if OS.windows? [then]
code...
end
Edit: was missing the ? on if condition.
Example used to test:
is_windows_host = "#{OS.windows?}"
puts "is_windows_host: #{OS.windows?}"
if OS.windows?
puts "Vagrant launched from windows."
elsif OS.mac?
puts "Vagrant launched from mac."
elsif OS.unix?
puts "Vagrant launched from unix."
elsif OS.linux?
puts "Vagrant launched from linux."
else
puts "Vagrant launched from unknown platform."
end
Execute:
# Ran provision to call Vagrantfile.
$ vagrant provision
is_windows_host: false
Vagrant launched from mac.
Here is a version using the Vagrant utils that checks for mac and windows:
if Vagrant::Util::Platform.windows?
# is windows
elsif Vagrant::Util::Platform.darwin?
# is mac
else
# is linux or some other OS
end
When I read the original question according to me it is not how to find out on which OS vagrant it self runs, but which OS do the virtual machines to be provisioned have. That is why you want to run a different provision script depending on the diffent OSses of the new VMs, eg: "/vagrant/provisioning/only_run_this_on_${OS_OF_NEW_VM}.sh".
Unfortunately Vagrant does not have this capability (yet), so this is my solution: I define my VMs on top of my vagrant file:
cluster = {
"control.ansible.RHEL76" => { :ip => "192.168.1.31", :type => 0, :cpus => 1, :mem => 1024, :box_image => "centos/7" },
"app01.ansible.RHEL76" => { :ip => "192.168.1.32", :type => 1, :cpus => 1, :mem => 1024, :box_image => "centos/7" },
"app02.ansible.RHEL76" => { :ip => "192.168.1.33", :type => 1, :cpus => 1, :mem => 1024, :box_image => "centos/7" },
"winserver" => { :ip => "192.168.1.34", :type => 2, :cpus => 1, :mem => 1024, :box_image => "mwrock/Windows2016" },
}
Then these conditions in my code can provision different ways depending on the OS of the VM's:
if "#{info[:box_image]}" == "mwrock/Windows2016" then
puts "is_windows_host: #{info[:box_image]}"
config.vm.provision : shell, inline => "/vagrant/provisioning/only_run_this_on_windows.psl"
end
if "#{info[:box_image]}" == "centos/7" then
puts "is_linux_host: #{info[:box_image]}"
config.vm.provision : shell, inline => "/vagrant/provisioning/only_run_this_on_linux.sh"
end
By the way, only the ansible controller should have ansible installed, because in real life (yes the office) I do not use vagrant but an ansible controller, which I also want in my lab (OK Virtual Box on both my Windows 10 desktop as well as my Ubuntu laptop). I use a condition to test "type = 0" (which I use for a ansible "controller"). Only the ansible controller starts ansible_local to provision the cluster of VMs with ansible.
if info[:type] == 0 then
cfg.vm.provision "shell", inline: "if [ `which ansible` ] ; then echo \"ansible available\"; else sudo yum -y update; sudo yum -y install epel-release; sudo yum -y install ansible; fi"
cfg.vm.provision "ansible_local" do |ansible|
ansible.extra_vars = { ansible_ssh_user: 'vagrant' }
ansible.inventory_path = "./production"
ansible.playbook = "rhelhosts.yml"
ansible.limit = "local"
end # ansible_local
This is displayed during a vagrant provision:
PS D:\Documents\vagrant\top> vagrant provision control.top.RHEL76
is_linux_host: centos/7
is_linux_host: centos/7
is_linux_host: centos/7
is_windows_host: mwrock/Windows2016
This is displayed during a vagrant provision:
PS D:\Documents\vagrant\ansible> vagrant provision control.ansible.RHEL76
is_linux_host: centos/7
is_linux_host: centos/7
is_linux_host: centos/7
is_windows_host: mwrock/Windows2016
--- many more lines, not relevant ---
Have fun experimenting and deploying your multimachine / multi OS labs!

How should we externalize variables in a Vagrantfile?

I have a vagrant file, where I want a variable "servers" to be used ...
# -*- mode: ruby -*-
# vi: set ft=ruby :
NUMM = 3
IP_OFFSET = 10
setup_master = File.read("master.sh")
setup_slave = File.read("slave.sh")
def ip_from_num(i)
"172.31.16.#{100+i+IP_OFFSET}"
end
# Map of servers -> parameters.
servers = {
0 => ["mybox","master.rhbd","ami-759dcb74","ap-northeast-1","subnet-4aa28b22","MASTER",ip_from_num(0)],
1 => ["mybox","slave1.rhbd","ami-759dcb74","ap-northeast-1","subnet-4aa28b22","SLAVE",ip_from_num(1)],
}
def getBox()
## this variable isnt available to vagrant...
servers[0]
end
Vagrant.configure("2") do |config|
(0..NUMM).each do |i|
config.vm.define "aws#{i}" do |n|
n.vm.box = getBox()
...
When this Vagrantfile is invoked, however, vagrant complains that the "servers" variable is not in existence. This makes sense : If vagrant is invoking from another class, and reading the configuration from that location, then the class variables defined in Vagrantfile might not be accessible in that scope.
So my question is : How can I make variables inside my Vagrantfile accessible to the outside provisioner? It seems to work okay with function calls (either because they are materialized during creation , or else because vagrant can easily call a function due to default scoping).
Another option could be, to turn the variables into functions - not nice, but if it is good enough for the circumstances:
# Map of servers -> parameters.
def servers()
{
0 => ["mybox","master.rhbd","ami-759dcb74","ap-northeast-1","subnet-4aa28b22","MASTER",ip_from_num(0)],
1 => ["mybox","slave1.rhbd","ami-759dcb74","ap-northeast-1","subnet-4aa28b22","SLAVE",ip_from_num(1)],
}
end
def getBox()
## this variable isnt available to vagrant...
servers()[0]
end
(Assuming servers is needed somewhere else too, otherwise it could just be a local variable in getBox.)

Resources