Run code in Vagrantfile only if provisioning - vagrant

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.

Related

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 ? :) )

Detect Vagrant up in vagrantfile

In my vagrantfile I use two lines of rudy code to get the username and password from the console and pass it on to the shell script for creating an additional user account instead of the default vagrant
This works well for vagrant up but it beats me by prompting the same when I run any other vagrant commands like vagrant status. Is there a way to catch if the vagrant file is called with up command?
require 'io/console'
print "Please enter username: "
#username = STDIN.gets
print "Please enter password: "
#password = STDIN.noecho(&:gets)
Vagrant.configure("2") do |config|
config.vm.box = "generic/debian10"
["Node 01"].each do |node_name|
config.vm.define node_name do |node|
node.vm.provision "shell" do |p|
p.args = [#username, #password]
p.path = "create_user.sh"
end
end
end
end
UPDATE
After #Matt Schuchard's suggestion, I've tried triggers; However, the global variables that are captured inside the trigger block are not shared with the place where I pass them into the shell script (p.args = [#username, #password])
config.trigger.before :up do |trigger|
if #credential_captured == nil # I've used this to prevent executing the following block per each vm
print "Please enter username: "
#username = STDIN.gets
print "Please enter password: "
#password = STDIN.noecho(&:gets)
#credential_captured = true
end
end
Sprinkle Some Plain Ruby into the Vagrantfile
The best practice would really be to put an idempotent provisioning block at the end that picks up your information from the environment or some other fixed location rather than trying to do this as interactive user input within the Vagrantfile. However, if you really want to do it the way you're doing it, just remember that a Vagrantfile is mostly just a DSL on top of Ruby.
You can get the status of a given virtual machine with vagrant status. For a single node, you could just wrap your current prompts at the top in a conditional. For example:
# assuming a single node named "default"
if %x(vagrant status default) =~ /running/
# prompt here
end
If you're running multiple nodes, then I'd suggest wrapping your prompts into a method and moving your conditional down into the body of the shell provisioning block you already have. For example:
def prompt_user
# prompt here
end
# ...
if `vagrant status #{node_name}` =~ running
prompt_user unless #username && #password
node.vm.provision 'shell' do |p|
# provision your node here
end
end
There are likely ways to make this code more efficient, and probably other ways to do what you want, but this approach is fairly straightforward and should at least get you headed in the right direction.

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

Running a specific provision block with Vagrant

I'm wanting to run a separate provisioning block in my Vagrant file on the newly provisioned server. At present when I run this from my CI server
vagrant up
the following blocks are executed successfully
config.vm.provider :linode do |provider, override|
#creates a new instance etc .. the following block runs on this instance
end
config.vm.provision :chef_solo do |chef|
chef.provisioning_path = "/tmp/deploy"
chef.cookbooks_path = ["cookbooks"]
chef.add_recipe = "mydeployagent"
end
now i want to run a separate provisioner afterwards. (a separate task in CI server) i.e.
config.vm.provision :chef_solo do |chef|
chef.provisioning_path = "/tmp/deploy"
chef.cookbooks_path = ["cookbooks"]
chef.add_recipe = "mydeploydatabaseagent"
end
I'm trying to figure out what I need to
run vagrant up so that it only executes the 1st provision block
run vagrant so that it will only run the 2nd provisioner block on the instance that was created in 1.
Thanks in advance
From Vagrant docs.
Vagrant.configure("2") do |config|
# ... other configuration
config.vm.provision "bootstrap", type: "shell" do |s|
s.inline = "echo hello"
end
end
The --provision-with flag can be used if you only want to run a
specific provisioner if you have multiple provisioners specified. For
example, if you have a shell and Puppet provisioner and only want to
run the shell one, you can do vagrant provision --provision-with
shell. The arguments to --provision-with can be the provisioner type
(such as "shell") or the provisioner name (such as "bootstrap" from
above).
Chef is about managing a desired state so you should be wanting it to ensure your two recipes are run at each time (and they should be idempotent).
I don't know any way to tell vagrant to use twice the same provisoner with different parameters (I can only think about ugly hacks in the vagrant file for that)
If the second recipe depends on the first one to have been executed then you may add a guard to skip this second recipe until the first one has run.
With chef-solo you may do something like this:
mydeployagent/recipes/default.rb
[...Whatever your recipe has to do (agent install, configuration, etc...]
# On a particular resource on your first recipe,
# trigger a notification to make a file which will be used as a guard for second recipe
remote_file "/opt/target/mydeplaoyagent" do
source [.. your source..]
notifies :create,"file[/opt/mydeployagent.initialized]"
end
file "/opt/mydeployagent.initialized" do
action :nothing
end
mydeploydatabaseagent/recipes/default.rb
#start the recipe by a guard to avoid doing anythng if first recipe has not been run
return unless ::File::exists?("/opt/mydeployagent.initialized")
[... your second recipe code here ...]
And in your vagrant file your can add the two recipes to your provisioner like:
config.vm.provision :chef_solo do |chef|
chef.provisioning_path = "/tmp/deploy"
chef.cookbooks_path = ["cookbooks"]
chef.add_recipe = "mydeployagent"
chef.add_recipe = "mydeploydatabaseagent"
end
Obviously the guard can be used on top of the first recipe too if it is not idempotent, but I highly encourage to rethink it to be able to run multiples times, you'll be happy to have it running when you'll have a configuration change to propagate in a file managed by this recipe (and trust me, you will have an update like this to manage one day).

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