How should we externalize variables in a Vagrantfile? - ruby

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.)

Related

In Ruby, how can I maintain a long list of instance variables from a file?

For example I store a lot of instance variables in a YAML file. This allow me to change the state of the program while it is running. However I need to change the method that reads the file every time I add a new variable.
e.g.
config = YAML.open_file 'config.yml'
#var1 = config["var1"]
#var2 = config["var2"]
#var3 = config["var3"]
#var4 = config["var4"]
#var5 = config["var5"]
...
How can I make this more dynamic and not need to change it as I add variables in the YAML file ?
Use Ruby meta-programming!
instance_variable_set is your friend here:
config = YAML.load_file 'config.yml'
config.each do |key,value|
instance_variable_set('#'+key, value)
end
Test:
puts #var1

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 source value from environmental variable

I am setting up a vagrant deployment with aws as the backend. I would like to source values from the shell. For instance
Vagrant.configure("2") do |config|
config.vm.box = "dummy"
config.vm.provider :aws do |aws, override|
aws.access_key_id = "YOUR KEY"
aws.secret_access_key = "YOUR SECRET KEY"
aws.session_token = "SESSION TOKEN"
aws.keypair_name = "KEYPAIR NAME"
aws.ami = "ami-7747d01e"
override.ssh.username = "ubuntu"
override.ssh.private_key_path = "PATH TO YOUR PRIVATE KEY"
end
end
I would like to populate the aws.{access_key_id,secret_access_key,etc} values from local environmental variables in BASH (e.g. $access_key_id, $secret_access_key, etc).
Is this possible to do directly in ruby or is there a specific vagrant DSL technique that allows for this?
Environment variables are exposed via the ENV hash, whose keys are environment variable names. For example,
aws.access_key_id = ENV['access_key_id']

Chef template loop: can't convert Chef::Node::immutableMash into String

I've got a Vagrant setup in which I'm trying to use Chef-solo to generate an conf file which loops though defined variables to pass to the application. Everything is working except the loop and I'm not familiar enough with Ruby/Chef to spot the error.
I'm going to lay out the whole chain of events in case there is something along the way that is the problem, but the first portions of this process seem to work fine.
A config file is written in yaml and includes env variable definitions to be passed:
...
variables:
- DEBUG: 2
...
The config file is read in by the Vagrantfile into a ruby hash and used to create the Chef json nodes:
...
settings = YAML::load(File.read("config.yaml"))
# Provision The Virtual Machine Using Chef
config.vm.provision "chef_solo" do |chef|
chef.json = {
"mysql" => {"server_root_password" => "secret"},
"postgresql" => {"password" => {"postgres" => "secret"}},
"nginx" => {"pid" => "/run/nginx.pid"},
"php-fpm" => {"pid" => "/run/php5-fpm.pid"},
"databases" => settings["databases"] || [],
"sites" => settings["sites"] || [],
"variables" => settings["variables"] || []
}
...
A bunch of chef cookbooks are run (apt, php, nginx, mysql etc) and finally my custom cookbook which is whats giving me grief. The portion of the cookbook responsible for creating a the conf file is shown here:
# Configure All Of The Server Environment Variables
template "#{node['php-fpm']['pool_conf_dir']}/vars.conf" do
source "vars.erb"
owner "root"
group "root"
mode 0644
variables(
:vars => node['variables']
)
notifies :restart, "service[php-fpm]"
end
And the vars.erb is just a one-liner
<%= #vars.each {|key, value| puts "env[" + key + " = " + value } %>
So, when I run all this chef spits out an error about not being able to convert a hash to a string.
can't convert Chef::Node::immutableMash into String
So for some reason this is coming across as an immutableMash and the value of key ends up being the hash [{"DEBUG"=>2}] and value ends up a nil object, but I'm not sure why or how to correct it.
The hash is ending up as the value of key in your example because the YAML file declares DEBUG: 2 as a list member of variables. This translates to variables being an array with a single hash member.
Try changing the template code to this:
<%= #vars[0].each {|key, value| puts "env[" + key + " = " + value } %>
Or try changing the YAML to this and not changing the template code:
variables:
DEBUG: 2
Either change will get your template loop iterating over the hash that you are expecting.

Resources