Redundant not_if? - ruby

I am taking over a project and we are working with chef automation and im going over the recipe setup and I am confused with this:
not_if do
existing_usernames = []
Etc.passwd {|user| existing_usernames << user['name']}
existing_usernames.include?(release_user)
end
Does this not_if statement ever run? The file has other things that seem to not be in use commented out

not_if is a guard for chef resource.
not_if: Prevent a resource from executing when the condition returns true.
so for you question, yes - it runs before chef resource is executed.

Related

in ruby/chef, what is the difference between "end if" and only_if"?

In chef (therefor, ruby), I've seen two ways of declaring conditionals
resource 'foo' do
echo "Ubuntu"
end if node['platform'] == 'ubuntu'
and
resource 'foo' do
echo "Ubuntu"
only_if node['platform'] == 'ubuntu'
end
Don't these effectively do the same thing? In the official docs, it seems "only_if" is the preferred way, and I can't find many examples of the "end if", but just curious if they, as they seem, do the same thing (execute the block only if the conditional is true).
Thanks!
Chef Resource Guard Clauses vs. Expression Post-Conditions
only_if is a guard clause that's part of the Chef DSL. However, do...end if is a Ruby modifier control expression (sometimes called a post-condition) applied to a block that functions the same way a normal Ruby if/then statement works. Note that even though the if is placed after the expression to be evaluated, the post-condition is still evaluated first.
Think of only_if as a Chef-specific resource statement. The other is just syntactic sugar supported by Ruby's interpreter, and the example you cited (assuming it works in Chef outside a Ruby resource block; I didn't bother to test it) is the same as writing the block inside a more standard if-statement like this one:
if node['platform'] == 'ubuntu'
resource 'foo' do
echo "Ubuntu"
end
end
Most Chef resources should follow the current style guides and DSL features, but post-conditions are very common in idiomatic Ruby because they emphasize the expression rather than the conditional and because they allow for more brevity of code.
There is a subtle difference in how only_if and end if behave when a node is converged (in Chef speak). In simple terms, when chef-client starts, it compiles the cookbooks and creates a collection of resources that will converge on the node.
For the sake of example, let's say we have a cookbook cookbook1 with only 1 resource in the recipe. When we run such cookbook in below scenarios:
Scenario 1:
Using do .. end if:
The resource is removed from the compilation when the condition is not matched. So there will be no resources to run. Example output from chef-client run when node['platform'] is not ubuntu.
Compiling Cookbooks...
Converging 0 resources
Scenario 2:
Using only_if guard
The resource remains in the collection, but it is skipped when node['platform'] is not ubuntu.
Compiling Cookbooks...
Converging 1 resources
Recipe: cookbook1::default
* resource[foo] action run (skipped due to only_if)
In short, pure Ruby code, such as if conditions will run during "compile" phase itself. Chef resources run during the "converge" phase. See the Chef Infra Client documentation for details.

Chef use cookbook_file in ruby block

I have the following code to figure out where Java is located on the box. Java comes with our application and what Java version that is included with the application differs.
def app_java_home
if Dir.exist?("#{app_home}/jre-server/linux")
Dir.chdir("#{app_home}/jre-server/linux") do
Dir.glob('jdk*').select { |f| File.directory? f }[0]
end
end
end
Then, in my cookbook I have
aws_s3_file "#{app_download_path}/#{app_s3['archive_file']}" do
bucket app_s3['bucket']
remote_path app_s3['remote_path']
region aws_region
not_if { ::Dir.exists?(app_bin_dir) }
not_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end
execute 'extract' do
user 'root'
command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
not_if { ::Dir.exists?("#{app_home}/ourapp") }
only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end
execute 'move' do
user 'root'
command "mv #{app_download_path}/ourapp/ #{app_install_path}"
not_if { ::Dir.exists?(app_home) }
end
cookbook_file "#{app_java_home}/jre/lib/security/local_policy.jar" do
source %W[#{app_release}/local_policy.jar default/local_policy.jar]
owner app_user_name
group app_group_name
mode 0755
end
cookbook_file "#{app_java_home}/jre/lib/security/US_export_policy.jar" do
source %W[#{app_release}/US_export_policy.jar default/US_export_policy.jar]
owner app_user_name
group app_group_name
mode 0755
end
However, the two cookbook_file resources fails because it can't find the directory:
No such file or directory # dir_chdir - /ourapp/jre-server/linux/
After a lot of googling, I've come to the conclusion that it's a .. "missmatch" (?) between compile time and run time of the recipes. Basically, if I understand it correctly, it tries to run the cookbook_file resource(s) first but fails. So never downloads, unpacks and installs the app artefact.
I've tried running app_java_home when the directory exists, and it does seem to work the way I want it..
I tried putting the cookbook_file resources in a ruby_block, but then I instead get:
undefined method `cookbook_file' for Chef::Resource::RubyBlock
The app_java_home .. function (?) used to look like this:
def app_java_home
"#{app_home}/jre-server/linux/#{jdk_version}"
end
Where jdk_version came from the databag. This worked fine, but we have a long standing bug/feature request in our system where it sometimes happens that "they" get the version they put in the databag wrong, causing all sorts of problems.. So they want a way to remove this dependency and instead "figure this out" dynamically.
Ruby and Chef isn't my forte, so I'm not sure what to try next. I have found references to Chef::Resources::CookbookFile (which, if I understand it, could/should be used inside ruby_blocks), but can't find any examples or documentation about it. The link on RubyDocs is broken.
Adding an answer here for a better explanation.
Any (Ruby) code that is not within any of the Chef resources, will run in Compile phase
All resource declarations will run in Convergence phase in the order they are defined
Thankfully, there is a way to make resources run in Compile phase if so required. Though IMHO it should be done sparingly and in exceptional cases.
As per your comment aws_s3_file and execute resources are the ones that unpack the app (and create the directory). In this case, it seems you want them to run in compile phase.
Prior to Chef client 16.0
Use the run_action option with the action that should be performed at the compile time. For example execute resource takes action :run:
# Note action ":nothing" and "run_action"
execute 'extract' do
user 'root'
command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
not_if { ::Dir.exists?("#{app_home}/ourapp") }
only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
action :nothing
end.run_action(:run)
Chef client 16.0 onwards
We can add a common property to the resources. Example with execute resource:
# Note the extra property "compile_time"
execute 'extract' do
user 'root'
command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
not_if { ::Dir.exists?("#{app_home}/ourapp") }
only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
compile_time true
end
And finally to answer the subject of the question:
Chef use cookbook_file in ruby block
This is not possible. Refer to the first point on the top. If we want Ruby code to run during converge (instead of compile), we put it within the ruby_block resource. So it can contain code like (for example):
ruby_block 'get directory' do
block do
def app_java_home
"#{app_home}/jre-server/linux/#{jdk_version}"
end
end
end
With the help of #seshadri_c, I finally managed to solve the problem! It took some doing, because I kept misunderstanding the suggestions etc.
So this is what I came up with (for posterity):
def jdk_version(required = true)
base_dir = "#{app_home}/jre-server/linux"
if Dir.exist?("#{base_dir}")
Dir.chdir("#{app_home}/jre-server/linux") do
Dir.glob("jdk*").each do |f|
if File.directory?(f)
return "#{f}"
end
end
end
end
end
def app_java_home
return "#{app_home}/jre-server/linux/#{jdk_version}"
end
Turns out I need to get just the version, individually, as well, so I rearranged the functions a bit. I'm sure it could be written much cleaner, but here the trick was to use return instead of puts/print! Well, I'm a programmer, but not a Ruby programmer so didn't know that was an option..
Then, in the cookbook, I added the .run_action() where needed. I didn't need them for the cookbook_file, which simplified things a bit:
aws_s3_file "#{app_download_path}/#{app_s3['archive_file']}" do
bucket app_s3['bucket']
remote_path app_s3['remote_path']
region aws_region
not_if { ::Dir.exists?(app_bin_dir) }
not_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end.run_action(:create)
execute 'extract' do
user 'root'
command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
not_if { ::Dir.exists?("#{app_home}/app") }
only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end.run_action(:run)
execute 'move' do
user 'root'
command "mv #{app_download_path}/app/ #{app_install_path}"
not_if { ::Dir.exists?(app_home) }
end.run_action(:run)
# JCE Unlimited Strength Jurisdiction Policy Files
cookbook_file "#{app_java_home}/jre/lib/security/local_policy.jar" do
source %W[#{app_release}/local_policy.jar default/local_policy.jar]
owner app_user_name
group app_group_name
mode 0755
end
cookbook_file "#{app_java_home}/jre/lib/security/US_export_policy.jar" do
source %W[#{app_release}/US_export_policy.jar default/US_export_policy.jar]
owner app_user_name
group app_group_name
mode 0755
end
With all that, everything is running exactly when they're supposed to and everything seems to be working.

How to do an "unless" conditional when changing permissions in a ruby_block in chef?

In chef I Have a ruby_block where I am changing permissions and ownership of a directory. How can I do a check where the permissions are only changed if they have not already been changed by the " FileUtils.chown" statement? I need to do this within the ruby_block if possible because i am ganna have other code in the ruby block. What would my "unless" statement be? Here is my code:
ruby_block 'exe' do
block do
FileUtils.chmod 0755, '/make/news'
FileUtils.chown('root', 'root', '/make/news')
end
end
The correct way to do this is to use Chef's file resource:
file '/make/news' do
mode 0755
owner 'root'
group 'root'
end
You're going down the road of trying to re-write the file resource which is not a good idea.
Using the Chef Resource's not_if Guard
Chef resources share a number of common functions. The ruby_block resource supports the not_if property as a conditional guard. The general format is:
ruby_block 'custom chmod' do
block do
#
end
not_if { true }
end
So, you could program your logic this way, but it will eventually bite you badly. Chef often works better if you use a file or directory resource declaratively using a separate block to manage permissions, and then (if necessary) chain it with a notification from some other block that needs a given permission set. For example:
directory '/make/news' do
mode '0755'
owner 'root'
group 'root'
action :nothing
end
ruby_block 'do something with news' do
block do
#
end
only_if { true }
notifies :create, 'directory[/make/news]', :before
end
That said, the goal of configuration management is to continuously converge, so I'd strongly question whether creating this interdependency between resource blocks is truly necessary in the first place. If possible, just converge your directory permissions every time to enforce them. While this may create a sequencing dependency within your recipe, a more declarative approach often simplifies cookbook and recipe debugging in the long run. Your individual mileage may vary.

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