How can I loop through bash routine in Chef? - bash

I have a bash script in Chef that fetches the time through NTP protocol from 3 instances running NTP server. The code at present is
if not node.run_list.roles.include?("ntp_server")
bash "ntpdate" do
code <<-EOH
/usr/sbin/ntpdate -s 10.204.255.15 10.204.251.41 10.204.251.21
EOH
end
end
This has been working just fine. However, I am supposed to automate the task such as if one of the instances is replaced, there is no manual intervention required to update the IP in the code above.
To achieve that, I have been successfully able to fetch the instances running the ntp_server role.
ntp_servers = search(:node, 'role:ntp_server')
Having done that, I am unable to add those IP's to the bash subroutine in Chef shown above in the code.
Can someone let me know how am I supposed to achieve that?

You shouldn't use bash block and call ntpdate with each chef run. ntpd should take care of clock being in sync, Chef has cookbook for this.
You could move IP addresses to the node and use join in code.
...
code "/usr/sbin/ntpdate -s #{node["ntp_ipaddresses"].join(" ")}"
...
Please, use ntp cookbook.

I managed to solve what I had posted in the question. The way I did it was using a Template and then the bash script. The recipe code now looks
ntp_servers = search(:node, 'role:ntp_server')
if not node.run_list.roles.include?("ntp_server")
template "/usr/local/bin/ntpdate.sh" do
source "ntpdate.sh.erb"
owner "root"
group "root"
mode 0644
variables(
:ntp_servers => ntp_servers
)
end
bash "ntpdate" do
user "root"
code <<-EOH
bash /usr/local/bin/ntpdate.sh
EOH
end
end
Having done this, I created a template in chef with the following configuration
#!/bin/bash
/usr/sbin/ntpdate -s <% #ntp_servers.each do |ntp_server| -%> <%= ntp_server['ipaddress'] %> <% end -%>
This way I could not dynamically add the ip addresses of the servers belonging to role ntp_server

Related

Generate a unique erb template for known hosts using the same cookbook - Chef

I have a number of hosts that needs to use different master server hosts and unique ports in a file. So the value of the ports and servers are known.
The end result is that my erb file should produce something of this nature for all the hosts that will run the cookbook:
PG_Host = -h myservername:unique_port
My challenge is how to get the values iterated in the recipe so depending on the short hostname when the server runs the cookbook, it picks up a specific port and a specific master server.
I am having difficulty matching the template with the erb file. I'd like some simple solutions on making this happen. Any pointers will be appreciated.
Here's my recipe:
template '/var/lib/pgsql/conf/mymonitor.sh' do
source 'mymonitor.sh.erb'
owner 'postgres'
action :create
variables(
master_server: 'someserver.fqn',
master_port: '897'
)
end
Template file:
PG_HOST= -h <%= #master_server %>:<%= #master_port %>
So how do I get it to churn out the similar files for anotherserver.fqn on port 5555 etc using some kind of a loop? I'm not sure how my variables for the other servers and their ports should look like.
The variables you pass to the template can be computed during chef-client runs, so in your recipe you can compute the port in the way you wish, and also the server name (master_server) can be calculated during chef-client run, or you can use some of the node's automatic attributes (collected by ohai) such for example node[:hostname] or node[:fqdn]. IE.:
Passing the recipes variables dynamically
server_port = "42#{node[:ipaddress].rpartition('.')[-1]}"
template '/var/lib/pgsql/conf/mymonitor.sh' do
source 'mymonitor.sh.erb'
owner 'postgres'
action :create
variables(
master_server: node[:fqdn],
master_port: server_port
)
end
Or you can use in your template node's attributes directly:
PG_HOST= -h <%= node[:fqdn] %>:<%= node[:mycookbook][:server_port] %>
or using string interpolation
PG_HOST= -h <%= "#{node[:fqdn]}:#{node[:mycookbook][:server_port]"} %>
EDIT:
The value of the attributes can be overridden for each node (or role), so easily you can assign different value for the attributes for each node. Check chef attribute documentation to see details about how attributes work.

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 variables between chef resources

i would like to show you my use case and then discuss possible solutions:
Problem A:
i have 2 recipes, "a" and "b".. "a" installs some program on my file system (say at "/usr/local/bin/stuff.sh" and recipe "b" needs to run this and do something with the output.
so recipe "a" looks something like:
execute "echo 'echo stuff' > /usr/local/bin/stuff.sh"
(the script just echo(es) "stuff" to stdout)
and recipe "b" looks something like:
include_recipe "a"
var=`/usr/local/bin/stuff.sh`
(note the backquotes, var should contain stuff)
and now i need to do something with it, for instance create a user with this username. so at script "b" i add
user "#{node[:var]}"
As it happens, this doesn't work.. apparently chef runs everything that is not a resource and only then runs the resources so as soon as i run the script chef complains that it cannot compile because it first tries to run the "var=..." line at recipe "b" and fails because the "execute ..." at recipe a did not run yet and so the "stuff.sh" script does not exist yet.
Needless to say, this is extremely annoying as it breaks the "Chef runs everything in order from top to bottom" that i was promised when i started using it.
However, i am not very picky so i started looking for alternative solutions to this problem, so:
Problem B: i've run across the idea of "ruby_block". apparently, this is a resource so it will be evaluated along with the other resources. I said ok, then i'd like to create the script, get the output in a "ruby_block" and then pass it to "user". so recipe "b" now looks something like:
include_recipe "a"
ruby_block "a_block" do
block do
node.default[:var] = `/usr/local/bin/stuff.sh`
end
end
user "#{node[:var]}"
However, as it turns out the variable (var) was not passed from "ruby_block" to "user" and it remains empty. No matter what juggling i've tried to do with it i failed (or maybe i just didn't find the correct juggling method)
To the chef/ruby masters around: How do i solve Problem A? How do i solve Problem B?
You have already solved problem A with the Ruby block.
Now you have to solve problem B with a similar approach:
ruby_block "create user" do
block do
user = Chef::Resource::User.new(node[:var], run_context)
user.shell '/bin/bash' # Set parameters using this syntax
user.run_action :create
user.run_action :manage # Run multiple actions (if needed) by declaring them sequentially
end
end
You could also solve problem A by creating the file during the compile phase:
execute "echo 'echo stuff' > /usr/local/bin/stuff.sh" do
action :nothing
end.run_action(:run)
If following this course of action, make sure that:
/usr/local/bin exist during Chef's compile phase;
Either:
stuff.sh is executable; OR
Execute it through a shell (e.g.: var=`sh /usr/local/bin/stuff.sh`
The modern way to do this is to use a custom resource:
in cookbooks/create_script/resources/create_script.rb
provides :create_script
unified_mode true
property :script_name, :name_property: true
action :run do
execute "creating #{script_name}" do
command "echo 'echo stuff' > #{script_name}"
not_if { File.exist?(script_name) }
end
end
Then in recipe code:
create_script "/usr/local/bin/stuff.sh"
For the second case as written I'd avoid the use of a node variable entirely:
script_location = "/usr/local/bin/stuff.sh"
create_script script_location
# note: the user resources takes a username not a file path so the example is a bit
# strange, but that is the way the question was asked.
user script_location
If you need to move it into an attribute and call it from different recipes then there's no need for ruby_blocks or lazy:
some cookbook's attributes/default.rb file (or a policyfile, etc):
default['script_location'] = "/usr/local/bin/stuff.sh"
in recipe code or other custom resources:
create_script node['script_location']
user node['script_location']
There's no need to lazy things or use ruby_block using this approach.
There are actually a few ways to solve the issue that you're having.
The first way is to avoid the scope issues you're having in the passed blocks and do something like ths.
include_recipe "a"
this = self
ruby_block "a_block" do
block do
this.user `/usr/local/bin/stuff.sh`
end
end
Assuming that you plan on only using this once, that would work great. But if you're legitimately needing to store a variable on the node for other uses you can rely on the lazy call inside ruby to do a little work around of the issue.
include_recipe "a"
ruby_block "a_block" do
block do
node.default[:var] = `/usr/local/bin/stuff.sh`.strip
end
end
user do
username lazy { "#{node[:var]}" }
end
You'll quickly notice with Chef that it has an override for all default assumptions for cases just like this.

Chef - create template with dynamic variable?

I'm having a bit of a challenge on a Chef recipe. I'm new to Chef, so please bear with me.
Step 1: My chef recipe installs Ruby Passenger, then compiles the Passenger nginx module along with Nginx.
# Install passenger and nginx module
bash "Install Passenger" do
code <<-EOF
source /usr/local/rvm/scripts/rvm
gem install passenger
EOF
user "root"
not_if { `gem list`.lines.grep(/^passenger \(.*\)/).count > 0 }
end
# Install passenger
# Note that we have to explicitly include the RVM script otherwise it won't setup the environment correctly
bash "Install passenger nginx module and nginx from source" do
code <<-EOF
source /usr/local/rvm/scripts/rvm
passenger-install-nginx-module --auto --prefix=/opt/nginx --auto-download
EOF
user "root"
not_if { File.directory? "/opt/nginx" }
end
Step 2: After that, I create the nginx config file using a template. This configuration requires the location of Passenger, which is dependent on step 1 completing.
template "/opt/nginx/conf/nginx.conf" do
source "nginx.conf.erb"
action :create
variables(
deploy_user: deploy_user,
passenger_root: `bash -c "source /usr/local/rvm/scripts/rvm; passenger-config --root"`.chomp,
passenger_ruby: `bash -c "source /usr/local/rvm/scripts/rvm; which ruby"`.chomp,
passenger: node[:passenger]
)
end
Problem: Chef appears to compile templates at th ebeginning of the run. So what ends up happening is that Step 2 is actually compiled before Step 1 is run. This means that the passenger_root variable is blank. It needs Step 1 to complete before being able to get the passenger_root, then run the template.
I tried wrapping the step 2 code in a ruby_block but that doesn't work: undefined methodtemplate' for Chef::Resource::RubyBlock`.
Not sure what to do here, or what is the best practice for Chef for something like this?
Thanks in advance,
Leonard
A cleaner and recommended way is to use Lazy Attribute Evaluation.
template "/opt/nginx/conf/nginx.conf" do
source "nginx.conf.erb"
action :create
variables lazy {
{
deploy_user: deploy_user,
passenger_root: `bash -c "source /usr/local/rvm/scripts/rvm; passenger-config --root"`.strip,
passenger_ruby: `bash -c "source /usr/local/rvm/scripts/rvm; which ruby"`.strip,
passenger: node[:passenger]
}
}
end
Also, I'd suggest using strip instead of chomp [thanks Draco].
As soon as you wrap your code in ruby_block you cannot use ordinary recipe resource declaration anymore. You have to write pure ruby code:
ruby_block "create /opt/nginx/conf/nginx.conf from template" do
block do
res = Chef::Resource::Template.new "/opt/nginx/conf/nginx.conf", run_context
res.source "nginx.conf.erb"
res.variables(
deploy_user: deploy_user,
passenger_root: `bash -c "source /usr/local/rvm/scripts/rvm; passenger-config --root"`.chomp,
passenger_ruby: `bash -c "source /usr/local/rvm/scripts/rvm; which ruby"`.chomp,
passenger: node[:passenger]
)
res.run_action :create
end
end
PS. And I guess you want to use strip instead of chomp to remove whitespace.
Yeah, Chef is a beast. I think part of the problem is there are a million ways to do the same things but there really is no documentation detailing the best way. What you probably want is to use Notifications, so that the block 1 runs first than notifies the block 2 to run. This means block 2 needs action :none so that it does not trigger until it gets notified.
I added the notify to your example in block 1 and added the action :none to block 2.
bash "Install Passenger" do
code <<-EOF
source /usr/local/rvm/scripts/rvm
gem install passenger
EOF
user "root"
not_if { `gem list`.lines.grep(/^passenger \(.*\)/).count > 0 }
notifies :run, 'bash[Install passenger nginx module and nginx from source]', :immediately
end
bash "Install passenger nginx module and nginx from source" do
code <<-EOF
source /usr/local/rvm/scripts/rvm
passenger-install-nginx-module --auto --prefix=/opt/nginx --auto-download
EOF
user "root"
action :none
end

How to define a function/action/... in chef that returns a value which can be used in e.g. not_if

I'm learning chef at the moment and I'm trying to write everything in a way that repeated provisioning doesn't break anything.
I have a server that is deployed on the machine and then there is some code loaded into it. The next time of provisioning I like to test first if the code has been loaded already. And I want to do it in a generic way because I use it in different recipes.
My idea would be to define a function/defintion/etc.. I can call the function which tests the condition and returns a value. My hopes would be that I can use this function/... in a not_if clause for other actions.
Is there a way to do this in chef with a defintion/action/provider/... or would I need to add some rubyish stuff somewhere?
Resources in Chef all have conditional execution.
The not_if and only_if statements can take a shell command as a string or a ruby block to determine if they should perform their action or not.
user "myuser" do
not_if "grep myuser /etc/password"
action :create
end
You might have a node attribute and use that as your conditional or call a ruby method that returns true or false.
template "/tmp/somefile" do
mode "0644"
source "somefile.erb"
not_if { node[:some_value] }
end
https://web.archive.org/web/20111120120013/http://wiki.opscode.com/display/chef/Resources#Resources-ConditionalExecution

Resources