Vagrant: different provisioner for different machines - ruby

I'm trying to have my vagrant configuration run a different shell script for each machine in my Multi-Machine environment.
I have a definition for smartos as well as one for centos, however I want to run a different shell provider configuration for each, before running the same chef-solo provider configuration on both.
#!/usr/bin/env ruby
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
$smartos_script = <<-SHELL
echo "http://10.40.95.5" > /opt/local/etc/pkgin/repositories.conf
rm -rf /var/db/pkgin && pkgin -y update
SHELL
$centos_script = <<-SHELL
touch /opt/my_file
SHELL
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.berkshelf.enabled = true
config.ssh.forward_agent = true
config.vm.define :smartos do |smartos|
smartos.vm.box = "smartos"
smartos.vm.box_url = 'http://dlc-int.openindiana.org/aszeszo/vagrant/smartos-base1310-64-virtualbox-20130806.box'
smartos.vm.guest = :solaris
config.vm.provision :shell do |shell|
shell.inline = $smartos_script
end
end
config.vm.define :centos do |centos|
centos.vm.box = "centos"
centos.vm.box_url = 'http://dlc-int.openindiana.org/aszeszo/vagrant/smartos-base1310-64-virtualbox-20130806.box'
config.vm.provision :shell do |shell|
shell.inline = $centos_script
end
end
config.vm.provision :chef_solo do |chef|
chef.add_recipe 'test'
end
end
I have also tried using smartos.vm.provision instead of config, but have seen no difference.
Does anyone have any idea how I can do this?

You were on the right track with
I have also tried using smartos.vm.provision instead of config
Try this simple Vagrantfile out
$smartos_script = <<-SHELL
touch /opt/foo
SHELL
$centos_script = <<-SHELL
touch /opt/bar
SHELL
# 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.define :smartos do |smartos|
smartos.vm.box = "smartos"
smartos.vm.box_url = 'http://dlc-int.openindiana.org/aszeszo/vagrant/smartos-base1310-64-virtualbox-20130806.box'
smartos.vm.provision :shell do |shell|
shell.inline = $smartos_script
end
end
config.vm.define :centos do |centos|
centos.vm.box = "centos"
centos.vm.box_url = 'http://dlc-int.openindiana.org/aszeszo/vagrant/smartos-base1310-64-virtualbox-20130806.box'
centos.vm.provision :shell do |shell|
shell.inline = $centos_script
end
end
end
When you run "vagrant up" and ssh into a machine, e.g. vagrant ssh smartos and cd to /opt you will see that the file "foo" has been created. And when you ssh into to cents machine you see that the file "bar" is created.

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

Vagrant machine unable to authenticate with my newly created user over ssh

My vagrantfile looks like this:
# -*- mode: ruby -*-
# vi: set ft=ruby :
vagrant_home = "/home/vagrant/"
local_share = "#{ENV['HOME']}"
unless Vagrant.has_plugin?("vagrant-vbguest")
puts "Vagrant plugin 'vagrant-vbguest' is not installed!"
puts "Execute: vagrant plugin install vagrant-vbguest"
end
unless Vagrant.has_plugin?("vagrant-sshfs")
puts "Vagrant plugin 'vagrant-sshfs' is not installed!"
puts "Execute: vagrant plugin install vagrant-sshfs"
end
Vagrant.configure("2") do |stage|
stage.vm.box = "centos/7"
stage.vm.hostname = "HSS-IAAS-VB"
stage.vm.box_check_update = true
stage.vm.network "private_network", :type => 'dhcp'
stage.vm.provider "virtualbox" do |vb|
vb.name = "centos7-dev"
vb.gui = false
vb.memory = "1024"
stage.ssh.keys_only = false
stage.ssh.username = "#{ENV['USER']}"
stage.ssh.forward_agent = true
stage.ssh.insert_key = true
stage.ssh.private_key_path = "#{ENV['HOME']}/.ssh/id_rsa" , "/home/#{ENV['USER']}/.ssh/id_rsa
stage.vm.provision :shell, privileged: false do |s|
ssh_pub_key = File.readlines("#{Dir.home}/.ssh/id_rsa.pub").first.strip
s.inline = <<-SHELL
echo #{ssh_pub_key} >> #{ENV['home']}.ssh/authorized_keys
sudo bash -c \"echo #{ssh_pub_key} >> #{ENV['home']}/.ssh/authorized_keys\"
SHELL
end
end
end
My issue is that when I run this vagrantfile, I receive an error that states the following: default: Warning: Authentication failure. Retrying... and if I run in debug mode I just see a bunch of timeouts..
All that I am trying to do is rather than create a "vagrant" user, I want to create a user that is the same as the user on the host machine by using #{ENV['USER']} and have the user immediately be able to run vagrant ssh and if their host user is test.user, then the guest user will be test.user..
vagrant ssh-config was:
Host default
HostName 127.0.0.1
User aaron.west
Port 2200
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile /Users/aaron.west/.ssh/id_rsa
IdentityFile /Users/aaron.west/.ssh/id_rsa
LogLevel FATAL
all help is appreciated :)
I believe you'll have to create a new user on your Vagrant machine. As per the docs for the ssh.username setting, it doesn't sound like that setting actually creates a user. It only helps you to tell Vagrant what user to connect as, if the box was made with a username other than vagrant.
You probably need to shell out to useradd during provisioning.

Shell provisioning in a multi-machine vagrantfile

How do I provision VMs created in a multi-machine vagrant file. I want to execute separate shell provisioning scripts in each of the machines created. I am unable figure out how vagrant facilitates this.
$kitCoreScript = <<SCRIPT
set -e
set -x
mkdir kitCoreFolder
exit
SCRIPT
$agentScript = <<SCRIPT
set -e
set -x
mkdir agentFolder
exit
SCRIPT
Vagrant.configure(2) do |config|
config.ssh.private_key_path = "rack_rsa"
config.vm.define "kitcore" do | kitcore |
kitcore.vm.provider :rackspace do |rs|
rs.username = "username"
rs.api_key = "1232134rewf324e2qede132423"
rs.admin_password = "pass1"
rs.flavor = /1 GB Performance/
rs.image = /Ubuntu 12.04/
rs.rackspace_region = :dfw
rs.server_name = "kit-core"
rs.public_key_path = "rack_rsa.pub"
end
kitcore.provision :shell, :inline => $kitCoreScript
end
config.vm.define "agents" do |agents|
agents.vm.provider :rackspace do |rs|
rs.username = "username"
rs.api_key = "2314rwef45435342543r"
rs.admin_password = "pass1"
rs.flavor = /1 GB Performance/
rs.image = /Ubuntu 12.04/
rs.rackspace_region = :dfw
rs.server_name = "agnet"
rs.public_key_path = "rack_rsa.pub"
end
agent.provision :shell, :inline => $agentScript
end
end
Apparently upon running the above vagrant script I get the below error message from vagrant.
dev-setup-scripts vagrant up
There are errors in the configuration of this machine. Please fix
the following errors and try again:
Vagrant:
* Unknown configuration section 'provision'.
Any help is highly appreciated.
You need to replace:
kitcore.provision
by
kitcore.vm.provision
Do the same for the vm called agent.

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.

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