Trying to loop over hash for Vagrant boxes, failing - ruby

Ok, i'm very new to Ruby (i come from PHP, Symfony2 and AngularJS) and relatively new when it comes to properly writing Vagrantfiles. I'm trying to create a multi-machine environment while trying to stick to DRY principles.
As i read that Vagrantfiles understand Ruby syntax, i looked up the way Ruby defines associative arrays. This happened to be quite easy, apparently not.
My Vagrantfile:
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
#hash for boxes: 'box_name' => 'last_ip_octet'
boxes = {
'frontend' => '10',
'qp' => '11'
}
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "chef/ubuntu-14.04"
#All machines, see the hash defined in top of the Vagrantfile for all the boxes
boxes.each do |key, value|
config.vm.define "#{key}.qp" do |#{key}_qp|
#{key}_qp.vm.network "private_network", ip: "192.168.51.#{value}"
#{key}_qp.vm.provision "shell", path: "../provisioning/agentinstall.sh"
#{key}_qp.vm.synced_folder "./share/#{key}.qp", "/var/www/html"
end
end
end
My problem reads as follows:
There is a syntax error in the following Vagrantfile. The syntax error
message is reproduced below for convenience:
/Users/Zowie/Documents/vagrant/project/temp/Vagrantfile:30: syntax error, unexpected keyword_end, expecting '|'
end
^
Unfortunately, i can't find any info on using Hashes or anything similar in Vagrantfiles.
I really hope you can help me out, because i'd not feel good while writing a super-long Vagrantfile with a lot of repetitions...
Thanks in advance!

The Stackoverflow website answered my question for me!
Thanks to Stackoverflow's code block feature, i noticed that my machine-specific configurations were commented out because i used a '#'.
I fixed it by using the following syntax in my loop (which is also easier to read):
boxes.each do |key, value|
config.vm.define "#{key}.qp" do |node|
node.vm.network "private_network", ip: "192.168.51.#{value}"
node.vm.provision "shell", path: "../provisioning/agentinstall.sh"
node.vm.synced_folder "./share/#{key}.qp", "/var/www/html"
end
end

Related

How to Add to Vagrant Settings From Loaded Vagrantfile

This may actually be a basic Ruby question more than a Vagrant-specific question. I have two Vagrantfiles. One makes a number of generic settings. The second one makes more specific settings for the particular instance. The first is loaded into the second. The idea is to keep common settings that are the same across all Vagrantfiles in one place so it's normalizes and I don't repeat myself. Some Vagrant settings are an array of strings. If I set one of these in the first Vagrantfile, and I try to add an element to it in the second file, I get an error:
Message: NoMethodError: undefined method `<<' for :__UNSET__VALUE__:Symbol
How can I add to an array?
Here is a boiled down first Vagrantfile with just what I'm interested in:
Vagrantfile.general
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure('2') do |config|
config.vm.provider 'cloud_service' do |cs|
cs.tags = ['ExampleTag1']
end
end
Vagrantfile (Specific)
load 'Vagrantfile.general'
Vagrant.configure('2') do |config|
config.vm.provider 'cloud_service' do |cs|
cs.tags << 'ExampleTag2'
end
end
The following works, but I want to know how to add to the previous array rather than overriding the whole array.
Vagrantfile (Workaround)
load 'Vagrantfile.general'
Vagrant.configure('2') do |config|
config.vm.provider 'cloud_service' do |cs|
cs.tags = ['ExampleTag1', 'ExampleTag2']
end
end
Note: I understand that the tags can be kept in a YAML file that is loaded by first and second files and then added to or subtracted from before setting the tags in the provider. I have read this answer elsewhere. What I am seeking is a more elegant way to handle this. Basically I want to know if it is possible to access the object created by the first Vagrantfile where the array is located and add to it programmatically in the second Vagrantfile using Ruby code.
Update
Here is one option that is Ruby code, but does not access the array inside the Vagrant object: set a global variable in the first Vagrantfile. I'd still like to see an answer that accesses the Vagrant config object and modifies the array.
First
# -*- mode: ruby -*-
# vi: set ft=ruby :
$cs_tags = ['ExampleTag1']
Vagrant.configure('2') do |config|
config.vm.provider 'cloud_service' do |cs|
cs.tags = $cs_tags
end
end
Second
load 'Vagrantfile.general'
$cs_tags << 'ExampleTag2'
Vagrant.configure('2') do |config|
config.vm.provider 'cloud_service' do |cs|
cs.tags = $cs_tags
end
end

Vagrant Multiple VM Provision Order

I'm trying to build a vagrant file that defines multiple VM.
Vagrant.configure("2") do |config|
config.vm.define "one" do |cfg|
[...]
end
config.vm.define "two" do |cfg|
[...]
end
end
My problem is that after the machine "two" is created and running, I want to run another script in the "one" machine.
I have tried with the experimental 'after" function, but I may use it in the wrong way.
Is it possible to do this with a pure vagrant script? (if true how ? :) )

Run a dynamic number of shell provisioners on the same VM in Vagrant without changing the Vagrantfile?

I am writing a Vagrantfile to set up a VM. There are some config parameters that I do not want to hardcode in the Vagrantfile, such as Memory and number of CPUs. As a consequence, I'm using a YAML file which gets loaded in the Vagrantfile to store these config parameters. One thing that is stored in the YAML file is the list of shell provisioner scripts to run. For instance:
---
machine_config:
mem: 2048
cpus: 2
provisioners:
-name: shell-script-1
path: <path-to-shell-script-1>
-name: shell-script-2
path: <path-to-shell-script-2>
---
The number of provisioners is not known a priori: in the YAML above there are two, but it's only an example. I'd like to have a Vagrantfile which can run all of the provisioners in the YAML file. What I mean is that I want to be able to add/remove provisioners to the YAML file without touching the Vagrantfile, yet the Vagrantfile should correctly run all of the provisioners in the YAML file. I searched on Google and there are plenty of examples on how to run the same, hardcoded provisioners on a dynamic number of VMs, but could find none for my problem.
What I'd like to do, written in pseudo-vagrantfile syntax, is:
require "yaml"
current_dir = File.dirname(File.expand_path(__FILE__))
yaml_config = YAML.load_file("#{current_dir}/machine_config.yaml")
machine_config = yaml_config["machine_config"]
additional_scripts = machine_config["provisioners"]
Vagrant.configure("2") do |config|
config.vm.box = <vm-box-to-use>
for each item $script in additional_scripts do
config.vm.provision "shell", path: $script["path"]
end
end
where machine_config.yaml is a YAML file like the one in the first example of this question, and $script is a variable that at every iteration of the loop holds a provisioner among those described in machine_config.yaml. As a last remark, I know nothing about Ruby and Ruby's syntax (maybe to someone with that knowledge the answer to my question is trivial, but I couldn't find it by googling).
The following will work
require "yaml"
current_dir = File.dirname(File.expand_path(__FILE__))
yaml_config = YAML.load_file("#{current_dir}/machine_config.yaml")
machine_config = yaml_config["machine_config"]
Vagrant.configure("2") do |config|
config.vm.box = "<vm-box-to-use>"
machine_config["provisioners"].each do |script|
config.vm.provision "shell", name: script['name'], path: script['path']
end
end

Vagrantfile ruby syntax explanation

I'm confused with the ruby syntax used to configure Vagrant. Especially with this construct. Is this an assignment, a method call, or something else? Is it pure ruby or vagrant specific dialect?
config.vm.network "forwarded_port", guest: 3000, host: 3000
And this one. Is the "ansible" an assignment or an argument, and where |ansible| comes from?
config.vm.provision "ansible" do |ansible|
ansible.playbook = "provisioners/docker.yml"
end
Where I can find more information about those specific expressions?
Those are DSLs, Ruby is a very good language for writing DSL, take a look a this other question
Although those are DSLs, you can throw vanilla Ruby code outside those blocks, and probably inside as well as long as it gets evaluated.
The Vagrantfile is written in standard Ruby syntax.
Here is an example Vagrantfile
Vagrant.configure("2") do |config|
# this is an evaluation statement
# .box is an string attribute
config.vm.box = "debian/stretch64"
# this is a method call
# .synced_folder is a method that takes two positional arguments ('synced_folder', '/vagrant'),
# followed by some keyword arguments (disabled: true)
config.vm.synced_folder 'synced_folder', '/vagrant', disabled: true
# this is a method call, followed by a "do ... end" block
# .provider is a method that takes one positional argument (:libvirt)
config.vm.provider :libvirt do |node|
# these are two evaluation statements
node.cpus = 4
node.memory = 4096
end
end
From the official documents,
you can see the variable "string" in "config.vm.box (string)",
but not following the methods config-vm-provider and config-vm-synced_folder.
I was also confused about the Vagrantfile syntax, even after reading the offical documents.
I think it's because Ruby looks very different to me compared to other languages I used before.

Render erb in Vagrant provisioning

As part of provisioning a VM, I need to create a configuration file at provision time. On my host machine, I'm able to pass variables to an erb file:
erb x=1 y=2 some_conf.erb
Is it possible to render the erb file on the host machine, and then pipe the result to the guest?
If I put the erb command in a config.vm.provision "shell" , the command is run on the guest machine (which does not have Ruby), e.g.
# this command runs on the guest
# where as I want erb to run on the host
config.vm.provision "shell", inline: <<-SHELL
erb x=1 y=2 some_conf.erb > /etc/some_conf
SHELL
Vagrant file is actually a ruby script so you could use the ruby erb template engine. Make sure to create the class necessary for your template
# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'erb'
Vagrant.configure("2") do |config|
template = ERB.new File.read("template/some_conf.erb")
p template.result(binding)
end
If you don't want to set up the new class etc and you have ruby2.2 installed you can just call erb from CLI directly, it will run on your host not the guest
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
exec ('erb x=1 y=2 template/some_conf.erb > some_conf')
end
once the some_conf file is generated on the host, you can use the vagrant file provisioner to push this single file on the host
config.vm.provision "file", source: "some_conf", destination: "/somewhere/on/the/guest/some_conf"

Resources