How do I include variables in my VagrantFile? - ruby

Can anyone guide me to how do I include variables in my VagrantFile? I am trying to inject configs into the Vagrantfile from an external file so that I can distribute the config to my colleagues without having them to hardcode configs directly on the Vagrantfile.
I had thought that since it was Ruby based I could just include a Ruby file but I get an error
Message: unintialized constant MyVars
My VagrantFile simplified
# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'vagrant.rb'
include MyVars
Vagrant.configure("2") do |config|
# Web
config.vm.define :joe do |joe|
joe.vm.box = "precise64_4.2.12"
joe.vm.hostname = WEBVMNAME
joe.vm.network :private_network, ip: "192.168.140.141"
# Port Forwarding
joe.vm.network :forwarded_port, guest: 22, host: 2201
joe.vm.network :forwarded_port, guest: 80, host: 8080
# Bootstrap Bash Script
joe.vm.provision :shell, :path => "bootstrap.sh"
end
end
And vagrant.rb contains
module MyVars
WEBVMNAME = "rex"
end
Do note that I am also a newbie at Ruby so I am not sure as well if its just the syntax I got wrong?
Edit: Updated code I am using

I use the approach of https://puphpet.com, I create a file config.yaml in the same directory of the Vagrantfile and...
In my Vagrantfile:
# encoding: utf-8
# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'yaml'
current_dir = File.dirname(File.expand_path(__FILE__))
configs = YAML.load_file("#{current_dir}/config.yaml")
vagrant_config = configs['configs'][configs['configs']['use']]
Vagrant.configure('2') do |config|
config.vm.network 'public_network', ip: vagrant_config['public_ip']
...
In my config.yaml:
---
configs:
use: 'home'
office:
public_ip: '192.168.2.117'
<more variables>...
home:
public_ip: '192.168.1.117'
<more variables>...

Use require_relative:
require_relative 'vagrant.rb'
include MyVars
# ...

Try changing your require to this:
require './vagrant'

I created a library directory:
require './lib/cfpEnvironment.rb'
include CFPEnvironment
And then did the scripting of what I need to be dynamic, defining the variables in the module created...
CFPPorts.select{ |key, value| value.numeric? }.each { |key, value|
config.vm.network :forwarded_port, guest: value, host: value
}
Thanks to #Matt and #strager for their answers above!

Related

How to write to a file provisioning from Vagrantfile

Hi I'm trying to add a Directory Index directive to the default VirtualHost for Apache from the Vagrantfile. I'm wondering if there is a way to edit a file from the Vagrantfile (I'm usung inline SHELL). I know I could copy an entire VH file to the guest machine, but I want to know how to write into files if possible.
Thanks!
You can do that with ansible like this:
config.vm.provision "ansible_local" do |ansible|
ansible.verbose = "vv"
ansible.become = true # execute as root
ansible.playbook = "relative_path_to_ansible_file/playbook.yml"
end
or with a shell
Vagrant.configure("2") do |config|
config.vm.provision "shell" do |s|
s.inline = "echo $1"
s.args = "'hello, world!'"
end
end
https://www.vagrantup.com/docs/provisioning/shell.html

How can I do computations on the fly within the vagrant file

How can I compute the hash on the fly within the vagrant file based on some input, for e.g. I do
# shell
$ echo "machine13" | sha256sum | cut -c1-12
2372240456c2
# vagrantfile
Vagrant.configure(2) do |config|
config.vm.hostname = "machine13"
config.vm.network "public_network", bridge: "eth0", use_dhcp_assigned_default_route: true, :mac => "2372240456c2"
end
If you just want to run a bash command and get its output to a variable in your Vagrantfile, you could use the ruby's back ticks to execute the command.
For example, your Vagrantfile:
# Vagrantfile
my_hostname = "machine12"
mac_hash = `echo #{my_hostname} | sha256sum | cut -c1-12`
Vagrant.configure(2) do |config|
config.vm.hostname = my_hostname
config.vm.network "public_network", bridge: "eth0", use_dhcp_assigned_default_route: true, :mac => mac_hash
end

vagrant provision for dev and production

We started using Vagrant to setup our development environment.
Now we would like to use the same Vagrantfile also in production/staging.
When I use the same vagrantfile that contains a virtualbox provider and gce I get the error
An active machine was found with a different provider. Vagrant
currently allows each machine to be brought up with only a single
provider at a time. A future version will remove this limitation.
Until then, please destroy the existing machine to up with a new
provider.
Machine name: default
Active provider: virtualbox
Requested provider: google
Is there a way I can vagrant up virtualbox and gce?
Vagrant.has_plugin?("nugrant")
Vagrant.require_version ">= 1.6.3"
Vagrant.configure("2") do |config|
config.vm.provider "virtualbox"
config.vm.box = "yungsang/boot2docker"
config.vm.provider :google do |google, override|
override.vm.box = "gce"
google.google_project_id = $GOOGLE_PROJECT_ID
google.google_client_email = $GOOGLE_CLIENT_EMAIL
google.google_key_location = $GOOGLE_KEY_LOCATION
# Override provider defaults
google.name = "name"
google.image = "ubuntu-1404-trusty-v20141212"
google.machine_type = "n1-standard-1"
google.zone = "europe-west1-c"
google.metadata = {'custom' => 'metadata', 'production' => 'app'}
google.tags = ['vagrantbox', 'prod']
override.ssh.username = $LOCAL_USER
override.ssh.private_key_path = $LOCAL_SSH_KEY
config.vm.define :prod do |prod|
prod.vm.provision :shell, inline: 'echo I am executed in prod only!!'
end
end
config.vm.synced_folder ".", "/vagrant"
# Fix busybox/udhcpc issue
config.vm.provision :shell do |s|
s.inline = <<-EOT
if ! grep -qs ^nameserver /etc/resolv.conf; then
sudo /sbin/udhcpc
fi
cat /etc/resolv.conf
EOT
end
# Adjust datetime after suspend and resume
config.vm.provision :shell do |s|
s.inline = <<-EOT
sudo /usr/local/bin/ntpclient -s -h pool.ntp.org
date
EOT
end
# Login docker hub
config.vm.provision :shell do |s|
s.inline = "/usr/bin/docker $#"
s.args = ["login", "-u", config.user.docker.username, "-p", config.user.docker.password, "-e", config.user.docker.email]
end
config.vm.provision :docker do |d|
d.pull_images "nginx"
d.pull_images "mongodb"
d.pull_images "java"
end
ACTICE_SPRING_PROFILE = "te"
config.vm.provision :docker do |d|
# provision docker stuff here
end
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 443, host: 8443
end
Update:
I tried to solve the problem with a multi vm setup, but now I am facing the problem that vagrant uses an outside-in provisioning, so my GCE specific scripts like gce.vm.provision :shell, inline: 'curl -sSL https://get.docker.com/ubuntu/ | sudo sh' are executed at the end not at the beginning.
Vagrant.configure("2") do |config|
config.vm.define "dev", primary: true do |dev|
dev.vm.provider "virtualbox"
dev.vm.box = "yungsang/boot2docker"
dev.vm.synced_folder ".", "/vagrant"
override.vm.provision :shell, inline: 'echo "Provision DEV"'
# Fix busybox/udhcpc issue
dev.vm.provision :shell do |s|
s.inline = <<-EOT
if ! grep -qs ^nameserver /etc/resolv.conf; then
sudo /sbin/udhcpc
fi
cat /etc/resolv.conf
EOT
end
# Adjust datetime after suspend and resume
dev.vm.provision :shell do |s|
s.inline = <<-EOT
sudo /usr/local/bin/ntpclient -s -h pool.ntp.org
date
EOT
end
dev.vm.network "forwarded_port", guest: 80, host: 8080
dev.vm.network "forwarded_port", guest: 443, host: 8443
end
config.vm.define "gce", autostart: false do |gce|
gce.vm.provider :google do |google, override|
override.vm.box = "gce"
google.google_project_id = $GOOGLE_PROJECT_ID
google.google_client_email = $GOOGLE_CLIENT_EMAIL
google.google_key_location = $GOOGLE_KEY_LOCATION
# Override provider defaults
google.name = "z-rechnung-#{TARGET_ENV}"
google.image = "ubuntu-1404-trusty-v20141212"
google.machine_type = "n1-standard-1"
google.zone = "europe-west1-c"
google.metadata = {'environment' => "#{TARGET_ENV}"}
google.tags = ['vagrantbox', "#{TARGET_ENV}"]
override.ssh.username = $LOCAL_USER
override.ssh.private_key_path = $LOCAL_SSH_KEY
gce.vm.provision :shell, inline: 'sudo apt-get update -y'
gce.vm.provision :shell, inline: 'sudo apt-get upgrade -y'
gce.vm.provision :shell, inline: 'curl -sSL https://get.docker.com/ubuntu/ | sudo sh'
end
end
# Login docker hub
config.vm.provision :shell do |s|
s.inline = "/usr/bin/docker $#"
s.args = ["login", "-u", config.user.docker.username, "-p", config.user.docker.password, "-e", config.user.docker.email]
end
config.vm.provision :docker do |d|
d.pull_images ....
end
config.vm.provision :docker do |d|
d.run "image" ....
end
The right answer is to remember that your Vagrantfile is just a Ruby program, first and foremost, and the execution of this program is to result in a datastructure that the CLI sub-commands traverse.
So, create functions that add provisioners to configuration, then call them in the "inside". For example,
def provisioner_one(config)
config.vm.provision :shell, 'echo hello'
end
Vagrant.configure('2') do |config|
# stuff here
config.vm.define 'dev' do |dev, override|
# whatever else here
provisioner_one(dev)
# other stuff here
end
# more other stuff here
end
This will DWIM.

Ruby unexpected ':', expecting kEND

I want to set up vagrant on my Ubuntu, when "vagrant up", it always give me the following error
syntax error, unexpected ':', expecting kEND
config.vm.provision :shell, path: "vagrantprov.sh"
I checked the Vagrantfile, it should be OK, can anyone tell me where the error is? Thanks.
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/trusty64"
# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"
config.vm.provider "virtualbox" do |vb|
vb.customize ["modifyvm", :id, "--memory", "1024"]
end
config.vm.provision :shell, path: "vagrantprov.sh"
end
Ruby <1.9? Old fashioned hash syntax style is required for old Ruby version
config.vm.provision :shell, :path => "vagrantprov.sh"
What version of Ruby are you running? The named args syntax (path: "...") is supported from on 1.9 and above, perhaps you have a lower Ruby version?
(1.8)
1.8.7 :001 > puts "a", b: 1
SyntaxError: compile error
(irb):1: syntax error, unexpected ':', expecting $end
(1.9)
1.9.3p429 :001 > puts "a", b: 1
a
{:b=>1}
=> nil
Ruby < 1.9 :
:a => 1
Ruby >= 1.9 :
a : 1

Override Vagrant configuration settings locally (per-dev)

I'd like the question to be answered in general, but to illustrate it, here's a use case:
I'm using Vagrant for a simple LMAP project. I use standalone Puppet for provisioning. Now, there might be some developers who sit behind a proxy and they would need some additional configuration to be made to the VM. I have things working on the Puppet side: I can pass the proxy IP (if any) as a fact to puppet in the Vagrantfile and Puppet reacts accordingly if it's set.
The only issue I have is: how can developers specify/override this setting for their development environment without having to change the Vagrantfile (which is under version control and must remain dev-environment-neutral)?
If would be awesome if people could override some Vagrant settings in a file called e.g. Vagrantfile.local, which I would exclude via .gitignore.
Since a Vagrantfile is just Ruby, I tried the following:
# Also load per-dev custom vagrant config
custom_vagrantfile = 'Vagrantfile.local'
load custom_vagrantfile if File.exist?(custom_vagrantfile)
The file inclusion basically works, but it looks like in the included file, I'm not in the same Vagrant context anymore...
Vagrant::Config.run do |config|
config.vm.provision :puppet do |puppet|
puppet.facter = { "proxy" => "proxy.host:80" }
end
end
... also "resets" all other puppet config values I made in the main Vagrantfile, which makes me think I'm heading in the wrong direction here. I should note that I'm a total noob at Ruby ;)
Can anyone give me a hint or even a working solution for how per-dev customization could be done here in general?
The Vagrantfile is just Ruby, so YAML is another option.
For example, in the Vagrantfile I do this:
# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'yaml'
settings = YAML.load_file 'vagrant.yml'
db_ip_address = settings['db']['ip_address']
api_ip_address = settings['api']['ip_address']
Vagrant.configure("2") do |config|
config.vm.box = "ffuenf/ubuntu-13.10-server-amd64"
config.vm.box_url = "https://vagrantcloud.com/ffuenf/ubuntu-13.10-server-amd64/version/4/provider/virtualbox.box"
config.vm.define "db" do |db|
db.vm.synced_folder settings['db']['artifacts_dir']['host'], settings['db']['artifacts_dir']['guest']
db.vm.network "private_network", ip: db_ip_address
... other stuff ...
end
config.vm.define "api" do |api|
api.vm.synced_folder settings['api']['artifacts_dir']['host'], settings['api']['artifacts_dir']['guest']
api.vm.network "private_network", ip: api_ip_address
api.vm.network "forwarded_port", guest: settings['api']['forwarded_port']['guest'], host: settings['api']['forwarded_port']['host']
end
end
Then I have a vagrant.yml file (I just made up the name; you can use whatever name you like) for the developer-specific configuration:
db:
ip_address: 192.168.4.14
artifacts_dir:
host: /Users/willie/myapp/db-scripts
guest: /opt/myapp/db
api:
ip_address: 192.168.4.15
forwarded_port:
host: 9080
guest: 8080
artifacts_dir:
host: /Users/willie/myapp/artifacts
guest: /opt/myapp/api
I would suggest using environment variables to dynamically change the behavior of the Vagrantfile without editing the file itself.
To give a real world example, here's how you could use an Ubuntu base box by default but have an environment variable define an alternative Linux distribution:
if ENV['OPERATINGSYSTEM']
if ENV['OPERATINGSYSTEM'].downcase == 'redhat'
os_name = 'centos'
config.vm.box = 'centos'
config.vm.box_url = 'https://dl.dropbox.com/u/7225008/Vagrant/CentOS-6.3-x86_64-minimal.box'
else
raise(Exception, "undefined operatingsystem: #{ENV['OPERATINGSYSTEM']}")
end
else
os_name = 'precise64'
config.vm.box = 'precise64'
config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
end
This example comes from https://github.com/puppetlabs/puppetlabs-openstack_dev_env
If you are prepared to define settings that are applied to all your vagrant boxes it's worth noting that, "Vagrant actually loads a series of Vagrantfiles, merging the settings as it goes." (ref https://docs.vagrantup.com/v2/vagrantfile/)
So I have the following defined in ~/.vagrant.d/Vagrantfile to increase the amount of RAM for my Vagrant boxes:
Vagrant.configure(2) do |config|
config.vm.provider "virtualbox" do |vb|
vb.memory = 2048
end
end
Here's an idea. It may be "ugly" and "wrong", but, at least, it works :)
# file2.rb, this is your per-dev configuration file
puts "included external file which uses outer var: #{foo}"
# file1.rb, this would be your Vagrantfile
puts 'first'
foo = 'bar'
external = File.read 'file2.rb'
eval external
puts 'second'
Let's run that
$ ruby file1.rb
first
included external file which uses outer var: bar
second
Adapting to your example, file2.rb would contain only usage of config without defining it (config will be provided from outer context)
config.vm.provision :puppet do |puppet|
puppet.facter = { "proxy" => "proxy.host:80" }
end
And your Vagrant file may look like this:
Vagrant::Config.run do |config|
external = File.read 'Vagrantfile.local'
eval external
# proceed with general settings here
config.vm.provision :puppet do |puppet|
puppet.facter = { "proxy" => "proxy.host:80" }
end
end
Update (another, "data-driven" approach)
# Vagranfile.local
config_values[:puppet][:facter][:proxy] = 'proxy.host:80'
# Vargantfile
Vagrant::Config.run do |config|
config_values = {
puppet: {
facter: {
proxy: nil
},
manifests_file: 'my_manifest.pp'
}
}
external = File.read 'Vagrantfile.local'
eval external # this should overwrite proxy config
# proceed with general settings here
config.vm.provision :puppet do |puppet|
if config_values[:puppet][:facter][:proxy]
puppet.facter = { "proxy" => config_values[:puppet][:facter][:proxy] }
end
puppet.manifests_file = config_values[:puppet][:manifests_file]
end
end
I believe that's the exact use case that Nugrant plugin was created to solve. It allows each of your devs to have a .vagrantuser (which is a .gitignore-ed file) in YAML specifying custom configuration values then reference these values with ease in Vagrantfile.
In your case, a proxied developer would have their .vagrantuser file looking like this:
proxy: 'proxy.host:80'
And your Vagrantfile would look like this (pseudo code, I don't really know ruby):
Vagrant::Config.run do |config|
config.vm.provision :puppet do |puppet|
if config.user.has_key?('proxy')
puppet.facter = { "proxy" => config.user.proxy }
end
end
end
You should bundle a sample/reference vagrantuser (i.e. vagrantuser.example) file for your devs to copy and adjust to their environment.
To extend on #Willie Wheeler 's answer. My setup is:
Root
|-- defaults.yml
|-- env.yml
|-- Vagrantfile
Vagrantfile
# Load local env config
require 'yaml'
dir = File.dirname(File.expand_path(__FILE__))
# defaults
settings = YAML::load_file("#{dir}/defaults.yml")
if File.exist?("#{dir}/env.yml")
env_settings = YAML::load_file("#{dir}/env.yml")
settings.merge!(env_settings)
end
...
# Customize the amount of memory on the VM:
vb.memory = settings["vb"]["memory"]
defaults.yml
vb:
memory: 1024
env.yml
vb:
memory: 204
This will merge whatever defaults you have with your per-dev config. Also it is clear to developers what values they can actually change
Consider using vagrant-proxyconf plugin. It allows to set proxy for all Vagrant VMs globally.
Another solution is to run external shell script during provisioning. I use separate config.vm.provision section at the beginning of Vagrantfile to do it:
# reset: true below is needed to reset the connection to the VM so that new
# environment variables set in /etc/environment will be picked up in next
# provisioning steps
config.vm.provision "shell", reset: true, inline: <<-SHELL
if [ -f /vagrant/Vagrantfile-settings.sh ]
then
/vagrant/Vagrantfile-settings.sh
fi
SHELL
Then just put a Vagrantfile-settings.sh file next to Vagrantfile, add it to .gitignore (or whatever) and put any script inside, for example to set proxy for interactive terminal, all daemons and docker containers:
# Proxy for interactive terminals
echo "http_proxy=http://PROXY_ADDRESS:PROXY_PORT" >> /etc/environment
echo "https_proxy=http://PROXY_ADDRESS:PROXY_PORT" >> /etc/environment
echo "no_proxy=127.0.0.1,localhost" >> /etc/environment
# Proxy for daemons (e.g. Docker deamon - used to pull images, apt - run from default daily cron job)
mkdir /etc/systemd/system.conf.d
echo [Manager] > /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"http_proxy=PROXY_ADDRESS:PROXY_PORT\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"https_proxy=PROXY_ADDRESS:PROXY_PORT\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"no_proxy=127.0.0.1,localhost\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "# Docker requires upper-case http proxy environment variables..." >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"HTTP_PROXY=http://PROXY_ADDRESS:PROXY_PORT2\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"HTTPS_PROXY=http://PROXY_ADDRESS:PROXY_PORT\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
echo "DefaultEnvironment=\"NO_PROXY=127.0.0.1,localhost\"" >> /etc/systemd/system.conf.d/01-http-proxy.conf
# Proxy for docker containers started with `docker run`
mkdir /home/vagrant/.docker
cat <<EOF > /home/vagrant/.docker/config.json
{
"proxies": {
"default": {
"httpProxy": "http:/PROXY_ADDRESS:PROXY_PORT",
"httpsProxy": "http://PROXY_ADDRESS:PROXY_PORT",
"noProxy": "127.0.0.1,localhost"
}
}
}
EOF
chown -R vagrant:vagrant /home/vagrant/.docker
You can load the settings from YAML file. This is demonstrated in Drupal VM as below:
# Use config.yml for basic VM configuration.
require 'yaml'
dir = File.dirname(File.expand_path(__FILE__))
if !File.exist?("#{dir}/config.yml")
raise 'Configuration file not found! Please copy example.config.yml to config.yml and try again.'
end
vconfig = YAML::load_file("#{dir}/config.yml")
So then you can create config.yml like:
vagrant_box: geerlingguy/ubuntu1404
vagrant_user: vagrant
vagrant_ip: 192.168.88.88
and in Vagrantfile you can use variables as:
config.vm.box = vconfig['vagrant_box']
config.vm.network "private_network", ip: vconfig['vagrant_ip']

Resources