Chef check for hostname? - ruby

It turns out I can't check for a node(host) name in Chef, so I'm trying to figure out the best way to make the following happen:
If hostname is X
ldap_access_filter = memberOf=<%= node['sssd_ldap']['ldap_access_node_filter'] %>
else
ldap_access_filter = memberOf=<%= node['sssd_ldap']['ldap_access_filter'] %>
end
The idea is that when the node name (or some matching variable) is true, then it uses the ldap_access_node_filter, which is a unique value, else, it uses the default value. I'm basically configuring sssd config, and one of the hosts requires a special ldap access filter.
If there is a better way, please let me know.
Please help.

Use
node.name
or
node.name.split('.')[0]

Ohai should allow you to get hostname?
If you want something node specific it seems like you could just plug in to the attribute precedence in chef. You would set a default value for the attribute maybe at the cookbook level and then set an explicit attribute on the node. It also means if you need 2 of these servers to have the value you don't change the cookbook just the config on the servers.

I just ran into this trying to deploy a special version of a file to one hostname. I used not_if, only_if
cookbook_file 'file/to/replace' do
not_if {node.name == 'host.domain.name'}
source 'file_version.4'
action :create
end
cookbook_file '/file/to/replace' do
only_if {node.name == 'host.domain.name'}
source 'file_version.5'
action :create
end

Related

apply recipe to all the servers whose hostnames matches regexp using CHEF

I'm creating a chef recipe to apply a configuration change on all servers whose hostname matches a specific pattern using regexp. However, I'm not sure how to do it.
Example: my hostname looks like this:
dvabwichf01
dvcdwichf01
my recipe in default.rb is :
case node['hostname']
when '*ab*'
template "/tmp/regextest" do
source "test_ab.erb"
mode "0644"
end
else
template "/tmp/regextest" do
source "test_cd.erb"
mode "0644"
end
end
But this is not working as expected, only the "else" template is updating on all servers. please assist.
You would need to use an actual regex, not a string like you have there (also you're using fnmatch glob matching, not a regex). That would only fix when the hostname is literally *ab*. A regexp literal in Ruby usually looks like /whatever/. so when /ab/ in this case.
I used switch for choosing values by adding a method in my helper file (in my case I put it into / app / helpers / application_helper.rb
Example below:
def name_of_your_method(hostname)
case hostname
when "Host1"
"template_1"
when "Host2"
"template_2"
when "Host2"
"template_3"
when "Host3"
"template_4"
else
"template_default"
end
end
Then in your code you would use the name in your method:
<%= user.hostname %>
And in your table(data) you would have a column for hostname(in this example)
Hope this helps

Chef conditional resource argument

I'm creating a user via Chef. His properties are stored in data bag:
{
"id": "developer",
"home": "/home/developer",
"shell": "/bin/zsh",
"password": "s3cr3t"
}
The recipe is:
developer = data_bag_item('users', 'developer')
user developer['id'] do
action :create
supports :manage_home => true
home developer['home']
comment developer['comment']
shell developer['shell']
password developer['password']
end
The problem is that if zsh is not installed on node, I cannot login as developer. So, I want to conditionally apply argument for user resource, like:
user developer['id'] do
action :create
supports :manage_home => true
home developer['home']
comment developer['comment']
if installed?(developer['shell'])
shell developer['shell']
end
password developer['password']
end
How can I achieve this?
To complement #mudasobwa's answer the proper way to do it in chef and avoid missing the shell if it's installed by another recipe or a package resource in the same recipe you have to use lazy attribute evaluation.
Long version for thoose interested on the how and why:
This is a side effect on how chef works, there's a first time compiling the resources to build a collection, at this phase any ruby code in a recipe (outside of a ruby_block resource) if evaluated. Once that is done the resources collection is converged (the desired state is compared to the actual state and relevant actions are done).
The following recipe would do:
package "zsh" do
action :install
end
user "myuser" do
action :create
shell lazy { File.exists? "/bin/zsh" ? "/bin/zsh" : "/bin/bash" }
end
What hapens here is that the evaluation of the shell attribute value is delayed to the converge phase, we have to use a if-then-else construction (here with a ternary operator as I find it more readable) to fallback to a shell we're sure will be present (I used /bin/bash, but a failsafe value would be /bin/sh) or the shell attribute will be nil, which is not allowed.
With this delayed evaluation the test on the presence of "/bin/zsh" is done after the package has been installed and the file should be present. In case there was a problem within the package, the user resource will still create the user but with "/bin/bash"
The easiest way to achieve what you want is to check for the shell existence explicitly:
shell developer['shell'] if File.exist? developer['shell']

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.

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.

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