Set an env var for only the provisioner - vagrant

I need an environment variable added to the front of $PATH that:
Doesn't last beyond the provisioning run.
Is dependent i.e. something will be installed earlier in the run that is then is available via $PATH, so I can't set it globally as this cookbook says to.
I tried the answer here:
Exec { environment => [ "foo=$bar" ] }
but I get the error Error: All resource specifications require names. When I add a name I get other errors about syntax, for which my fiddling around to fix just gives me other errors (the error Syntax error at '}'; expected '}' is my favourite!)
I've tried using export to set it, but I see: Error: Could not find command 'export'
I've tried using set and setenv too, with similar results. There must be a straightforward way to do this, but I can't find it.
Edit
Just to add, these are the available shells:
$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
/bin/zsh
/usr/bin/zsh
zsh is part of the provisioning, but it could be a requirement of the answer, if needs be.

Added to the front of your path, you want to add your resource default like this I believe:
Exec { environment => "PATH=value:$PATH", }
This could be incorrect, but I do know that it will replace the variables you set, not append to them by default. More details at https://docs.puppetlabs.com/puppet/latest/reference/type.html#exec-attribute-environment

I tried a few ways for this, but the best I found was to use Hiera. I read quite a few blogs on how to set this up with Vagrant too, but this was the best I found.
My project directory layout
Vagrantfile
pp/
manifests/
modules/
data/
hiera.yml
common.yml
Vagrantfile
The relevant part of the Vagrantfile:
config.vm.provision "puppet" do |puppet|
puppet.manifests_path = "pp/manifests"
puppet.module_path = "pp/modules/custom"
puppet.manifest_file = "default.pp"
puppet.hiera_config_path = "pp/data/hiera.yaml"
end
I've no idea yet why there needs to be a hiera.yaml which points to a common.yaml, but that's the way it is.
hiera.yaml
---
:backends:
- yaml
:hierarchy:
- "common"
:yaml:
:datadir: '/vagrant/pp/data'
common.yaml
---
ruby_version: "2.3.0"
ruby_prefix: "/opt/rubies"
...
Then in a manifest
$ruby_version = hiera("ruby_version")
$ruby_prefix = hiera("ruby_prefix")
$ruby_dir_fullpath = "${ruby_prefix}/ruby-${ruby_version}"
Seems like a lot of effort to me, but again, that's the way it is.

Related

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

System vs Local Environment in Rakefile

We're using rake for some setup steps. We need to make sure that a particular directory is in the PATH, and we've used this in Rakefile to do so:
hasImageMagic = ENV["Path"] =~ /ImageMagick/
if not hasImageMagic ...
When run Rake under git-bash, this works fine.
When we run Rake run under Powershell however, this fails to find the element in our Path. We've found that it's looking at the user's local Path rather than the system path.
Does ruby have a way to look at the system path variable?
You can use the GetEnvironmentVariable() method to specify that it's a machine-wide environment variable you're interested in:
$PathVar = [Environment]::GetEnvironmentVariable("Path",[System.EnvironmentVariableTarget]::Machine)
if($PathVar -notmatch "ImageMagick"){
# error handling here
}
#StevenV sorry, this is my fault. I had originally written this code and only tested it in gitbash:
hasImageMagic = ENV["Path"] =~ /c:\\Program Files\\ImageMagick-/
if not hasImageMagic ...
While that worked fine in gitbash, I was later alerted by another team member that this did not work as expected in powershell. I then pushed the code you posted in your question, and it does seem to work with powershell:
hasImageMagic = ENV["Path"] =~ /ImageMagick/
if not hasImageMagic ...
I am personally still in the dark as to why gitbash liked the original regex but powershell did not. This is my first ruby code ever, so mea culpa.

Chef cookbook - copy file to local user with '~' ruby variable

Well, let's start by saying I'm a chef noob and I am trying to hash this code out.
I am in a full mac shop. I am using Chef to automate system wide changes. As I'm new, I'm rolling it out onto our Mac AV systems.
Basically, there is a folder on a file server that has MAC SCREEN SAVERS directory. I copy the server directory locally to the MAC OS X /User/user_name/Pictures directory.
So, this is what I got in chef:
local_folder_modified = File.mtime("~/Pictures/SCREEN SAVER NEW MACS")
server_folder_modified = File.mtime("/Volumes/SERVER/SCREEN\ SAVER\ NEW\ MACS/")
if server_folder_modified != local_folder_modified
# file has changed
then
require 'fileutils'
FileUtils.cd('server_folder_modified') do
FileUtils.rm('local_folder_modified/*')
FileUtils.cp_r './*', 'local_folder_modified'
Else
end
end
Anyways, I can't figure how to set the '~' to be the running user of this recipe. So, if Comp_A has user Jim_Beam and Comp_B has user Jack_Daniels, I don't want to set the code to be:
ENV[HOME] = /user/jimbeam
As it won't work on Jack_Daniels. Right?
I've read that file.expand will work, or ENV, but I am really unsure what will be the best code to say
"hey, I want the current user that will need this screen saver - so set the environment as a variable so it works across different nodes".
Anyways, thanks for your help. I hope I am making sense!
Yes, use File.expand. It will expand the tilde ~ to be the the home directory of the user running this cookbook. Alternatively, you could do:
"#{ENV['HOME']}/Pictures/SCREEN SAVER NEW MACS"
Like the previous comment, this is not chef DSL or ruby code. What is the source of this code or is it just pseudo-code to ask the question?
Also, chef-client is not frequently run as multiple users in a chef server deployment. It's usually run in a sudo context. So maybe you are referring to a --local-mode or chef-zero application?
You may want to use file stat of /dev/console to get the current user. Depending how you are running the chef-client Env[‘Home’] might not give you want you want. Try this:
console_user = Etc.getpwuid(::File.stat("/dev/console").uid).name
home_dir = ::File.join(‘Users’, console_user)
You can see that the chef launchd provider uses this method to determine the console user
Also there is a much simpler way to do what you are trying to accomplish with the remote_file resource. Try this:
console_user = Etc.getpwuid(::File.stat("/dev/console").uid).name
home_dir = ::File.join(‘Users’, console_user)
pics = ::File.join("#{home_dir}/Pictures/")
server_base_url = "https://PLACE_WHERE_STORE/Wallpapers")
[
‘Pic1’,
‘Pic2’,
].each do |pic|
remote_file ::File.join(pics, pic) do
source “#{server_base_url}/#{pic}”
owner console_user
group console_user
mode '0755'
action :create
end
end
For added security you should also include checksum

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