makefile pass variable to vagrantfile and start the vagrant VM - shell

I'm writing the automated deployment plan for the project. I plan to use makefile to control the vagrant vm and its operations. I want to place all user option configurations in the makefile, including some vagrantfile configuration parameters, such as CPU, IP, and the like. But how do I pass makefile parameters to vagrantfile?
I test to use shell
CPU_NUM ?= 3
init:
vagrant_cpu= $(CPU_NUM ) vagrant up
But I didn't know vagrantfile how to obtain it
I would very appreciate it if you guys can tell me how to achieve it that parameters are passed from the makefile to vagrantfile

Makefile is nice, but why not just use shell scripts for general-purpose scripting? But, to actually answer your question:
The command where you set the environment variable is wrong, the = must not have a space on either side. Change it to:
init:
vagrant_cpu=$(CPU_NUM) vagrant up
A Vagrantfile is just a Ruby script. This means that you can access environment variables the same way you would in Ruby, using ENV. In your Vagrantfile, you could have something like:
config.vm.provider "virtualbox" do |v|
v.memory = 1024
v.cpus = ENV["vagrant_cpu"].to_i
end
Note to_i to convert to integer.

Related

Can I define the hostname (or other variable) on the vagrant command line?

I want to do something like this:
vagrant up --hostname="hello.world"
However there's no --hostname.
The reason I want this is so that the provisioner (Ansible) can use it to modify the provisioning depending on the hostname.
I could also use vagrant up hello.world (in which case Ansible could use it as inventory_hostname), but in that case I'd need to create an entry for hello.world in the Vagrantfile, which I don't want, since all possible hosts use the same Vagrantfile configuration. (An alternative could be to somehow specify in the Vagrantfile "use this configuration regardless of vm-name", but I don't know how to do that either.)
If none of this works, I could use an environment variable, but I don't know how to process that in Vagrantfile either.
You can call vagrant like:
HOSTNAME=myhostname vagrant up
In your Vagrantfile, store the environment variable:
$hostname = #{ENV['HOSTNAME']}"
And then call your ansible playbook passing extra_vars:
ansible.extra_vars = {
hostname: "#{$hostname}"
}
Alternatively, if these hostnames are predefined in groups in your playbook you can use limit:
ansible.limit = "#{$hostname}"

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

How to detect which OS type the current guest is in the Vagrantfile

I'd like to have one Vagrantfile for multiple different OS types (e.g. windows and linux). I'm currently attempting to do something like this:
...
if config.vm.guest.to_s == "windows"
# ... do something windows specific
else
# ... do something linux specific
end
However, config.vm.guest returns an Object instance, not a string or a symbol, even though it was set in the base box Vagrant file (i.e. config.vm.guest = :windows).
How can I do this?
I'll bet you're trying to do this in a ~/.vagrant.d/Vagrantfile. The problem is that ~/.vagrant.d/Vagrantfile is run before the Vagrantfile in your current directory.
So it cannot know anything about the configuration before hand.

Run code in Vagrantfile only if provisioning

I want to display some text on the screen when running vagrant up (or vagrant provision, etc.) if and only if provisioning is being done. (For vagrant up it is only run the first time, or if specifically forced with --provision.)
How can this be done?
Adding a shell provisioner is probably the easiest solution, with the small cost that it is executed on the VM over SSH.
Another option is to use the vagrant-host-shell plugin:
Vagrant.configure('2') do |config|
# other config and provisioners
# [...]
config.vm.provision :host_shell, inline: 'echo "Provisioned!"'
end
If you like over-engineering, you can even make your own plugin in Vagrantfile. ;)
class EchoPlugin < Vagrant.plugin('2')
class EchoAction
def initialize(app, env)
#app = app
end
def call(env)
#app.call(env)
puts "Provisioned!"
end
end
name 'echo'
action_hook 'echo' do |hook|
hook.before Vagrant::Action::Builtin::Provision, EchoAction
end
end
Vagrant.configure('2') do |config|
# ...
end
According to the Vagrant issue #7043 where somebody wanted to use #env[:provision_enabled] to see if provisioning is being run. It was answered that you could also check the arguments your Vagrantfile was called with:
This is not currently possible because the Vagrantfile is parsed before the environment is created. This information is available to
provisioners and plugins, but not the Vagrantfile itself because of
the load ordering. In other words, that #env doesn't exist until after
all Vagrantfile's have been parsed, and unfortunately that's a hard
requirement because information in the Vagrantfile determines the way
that object is created. It's a catch-22 for your use case.
One possible alternative is to inspect ARGV in your Vagrantfile.
Something like:
if ARGV.include?("up") || (ARGV.include?("reload") && ARGV.include?("--provision"))
...
end
Example usage
I added two functions to the bottom of my Vagrantfile:
def provisioned?(vm_name='default', provider='virtualbox')
File.exists?(File.join(File.dirname(__FILE__),".vagrant/machines/#{vm_name}/#{provider}/action_provision"))
end
def explicit_provisioning?()
(ARGV.include?("reload") && ARGV.include?("--provision")) || ARGV.include?("provision")
end
Which I can use around any statement in my Vagrantfile:
if (not provisioned?) || explicit_provisioning?
...
end
I'm not sure if I understood your question correctly, but if you want to show a text message if and only if provisioning runs, and you already know that provisioning runs only on first vagrant up and when forcing it using the --provision switch - then why not just add the output of the message to the provisioning itself?
This could be as simple as using a shell provisioner and running an echo command inside of that.
As Vagrant supports multiple provisioners within one Vagrantfile and is able to run all of them when provisioning a virtual machine, this is a dead-easy step, no matter whether you use the shell provisioner anyway, or if you use any other provisioner.

Passing variable to a shell script provisioner in vagrant

I'm using a simple shell script to provision software for a vagrant setup as seen here.
But can't figure out a way to take the command line arguments passed in to vagrant and send them along to an external shell script. Google reveals that this was added as a feature but I can't find any documentation covering it or examples out there.
You're correct. The way to pass arguments is with the :args parameter.
config.vm.provision :shell, :path => "bootstrap.sh", :args => "'first arg' second"
Note that the single quotes around first arg are only needed if you want to include spaces as part of the argument passed. That is, the code above is equivalent to typing the following in the terminal:
$ bootstrap.sh 'first arg' second
Where within the script $1 refers to the string "first arg" and $2 refers to the string "second".
The v2 docs on this can be found here: http://docs.vagrantup.com/v2/provisioning/shell.html
Indeed, it doesn't work with variables!
The correct snytax is :
var1= "192.168.50.4"
var2 = "my_server"
config.vm.provision :shell, :path => 'setup.sh', :args => [var1, var2]
and then, in the shell setup.sh:
echo "### $1 - $2"
> ### 192.168.50.4 - my_server
Here is alternative way of passing the variables from the environment:
config.vm.provision "shell" do |s|
s.binary = true # Replace Windows line endings with Unix line endings.
s.inline = %Q(/usr/bin/env \
TRACE=#{ENV['TRACE']} \
VERBOSE=#{ENV['VERBOSE']} \
FORCE=#{ENV['FORCE']} \
bash my_script.sh)
end
Example usage:
TRACE=1 VERBOSE=1 vagrant up
For adding explicit arguments, I used this successfully:
config.vm.provision "shell", path: "provision.sh", :args => "--arg1 somearg --arg2 anotherarg"
Answering my own question based on some info I found in an old version of the docs page:
config.vm.provision :shell, :path => "bootstrap.sh", :args => "'abc'"
-- #user1391445
In new versions You can use array:
config.vm.provision :shell, :path => "bootstrap.sh", :args:["first", "second"]
For anyone who is looking NOT just for a quick fix but for a clean, sane solution that will withstand the test of time :), here is an architectural perspective:
You can use a library, but that complicates things:
when the library changes, you will need to upgrade it and potentially fix the Vagrant file => more work, more headaches
when the Vagrant version changes, you might have to update the Vagrant file => more work, more headaches
You can pass the variables and extract their values via EVN['var_name'] as shown in the Tips & Tricks section of the Vagrant docs. But that removes the simplicity from "vagrant up". Now you have to remember what you are passing in every time and you need to type it correctly => more fat-finger errors, more headaches, more time (This comes from the bottom of the page - last tip called Overwrite host locale in SSH session). But at least you don't have to maintain the library along with all other corollary implications.
Create and manage the variables in the external shell script, or better yet, in a .json blob that the script is consuming - that way you avoid the shrapnel of changes going across your "vagrant up" invocation, your Vagrant file, and finally into your external shell script = > problems will be minor, if any; you know exactly where they will be => easy to configure, easy to fix => little to no headaches, MORE time :)
That said, there might me exceptions to No.3 above where the values truly belong in the Vagrantfile, i.e., settings that pertain to the external configuration of the VM, like host and guest ports, etc. On the other hand, anything that you configure on the VM itself, like new users and their passwords, should happen outside the Vagrantfile as described in No.3 above.

Resources