In my recipe I have following resource that restart the computer but I want to apply guard on this resource.
powershell_script "Restart Computer" do
code <<-EOH
$computer_name=$env:COMPUTERNAME
Restart-Computer -ComputerName $computer_name
EOH
guard_interpreter :powershell_script
only_if reboot_pending?
end
I googled and found following ruby script for checking machine required reboot or not
def reboot_pending?
# don't break when used as a mixin in contexts without #node (e.g. specs).
if self.respond_to?(:node, true) && node.run_context.reboot_requested?
true
elsif platform?("windows")
# PendingFileRenameOperations contains pairs (REG_MULTI_SZ) of filenames that cannot be updated
# due to a file being in use (usually a temporary file and a system file)
# \??\c:\temp\test.sys!\??\c:\winnt\system32\test.sys
# http://technet.microsoft.com/en-us/library/cc960241.aspx
registry_value_exists?('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }) ||
# RebootRequired key contains Update IDs with a value of 1 if they require a reboot.
# The existence of RebootRequired alone is sufficient on my Windows 8.1 workstation in Windows Update
registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') ||
# Vista + Server 2008 and newer may have reboots pending from CBS
registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired') ||
# The mere existence of the UpdateExeVolatile key should indicate a pending restart for certain updates
# http://support.microsoft.com/kb/832475
(registry_key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') &&
!registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0].nil? &&
[1,2,3].include?(registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0][:data]))
elsif platform?("ubuntu")
# This should work for Debian as well if update-notifier-common happens to be installed. We need an API for that.
File.exists?('/var/run/reboot-required')
else
false
end
end
I tried to add this method inside guard but its not working.Is there any way to write multiline statement inside chef guard?
Chef guards take in a block, like the first example here. Blocks can be multi line, but can't have methods declared inside of them. If you copy the contents of the method you found and put them inside the only_if that should work.
powershell_script "Restart Computer" do
...
only_if do
<contents of method here>
end
end
While this will work, the cleaner way to implement such an extensive guard is to put the reboot_pending method in a library and call that method from your recipe.
Related
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.
The challenge prompt is above, and my latest attempt is below. The directories and files are created as expected, and the read-out after executing chef-apply multipleCopies.rb tells me the files are linked, but when I update any one of the files, the others do not follow suit. Any ideas? Here is my code:
for x in 1..3
directory "multipleCopy#{x}" do
mode '0755'
action :create
end
end
file "multipleCopy1/secret.txt" do
mode '0755'
action :create
end
for x in 2..3
link "multipleCopy#{x}/secret.txt" do
to "multipleCopy1/secret.txt"
link_type :hard
subscribes :reload, "multipleCopy1/secret.txt", :immediately
end
end
Note: For less headache, I am testing the recipe locally before uploading to the ubuntu server referenced in the prompt, which is why my file paths are different and why I have not yet included the ownership properties.
So a file hard link doesn't seem to be what the question is going for (though I would say your solution is maybe better since this is really not what Chef is for, more on that later). Instead they seem to want you to have three actually different files, but sync the contents.
So first the easy parts, creating the directories and the empty initial files. It's rare to see those for loops used in Ruby code, though it is syntactically valid:
3.times do |n|
directory "/var/save/multipleCopy#{n+1}" do
owner "ubuntu"
group "root"
mode "755"
end
file "/var/save/multipleCopy#{n+1}/secret.txt" do
owner "root
group "root"
mode "755"
end
end
But that doesn't implement the hard part of sync'ing the files. For that we need to first analyze the mtimes on the files and use the most recent as the file content to set.
latest_file = 3.times.sort_by { |n| ::File.mtime("/var/save/multipleCopy#{n+1}/secret.txt") rescue 0 }
latest_content = ::File.read("/var/save/multipleCopy#{latest_file+1}/secret.txt") rescue nil
and then in the file resource:
file "/var/save/multipleCopy#{n+1}/secret.txt" do
owner "root
group "root"
mode "755"
content latest_content
end
As for this not being a good use of Chef: Chef is about writing code which asserts the desired state of the machine. In the case of files like this, rather than doing this kind of funky stuff to check if a file has been edited, you would just say that Chef owns the file content for all three and if you want to update it, you do it via your cookbook (and then usually use a template or cookbook_file resource).
I'm writing a chef script to install nvm for windows. I've just had a problem where the system ends up with a User and a System variable for NVM_HOME
env 'NVM_HOME' do
action :delete
end
the above would presumable remove both
How can I amend my chef script to ensure the system variable is kept but the user variable is removed if present?
If the user environment variable is the user running chef and not another user you can use the registry_key resource to ensure the values are deleted (untested example):
registry_key "HKCU\\Environment" do
values [{:name => 'NVM_HOME'},{:name => 'NVM_SYMLINK'}]
action :delete
end
If you wish to change another user variable you can have a look at this answer
Did this in the end
[
'NVM_HOME',
'NVM_SYMLINK'
].each do |key|
execute "ensure nvm has not added #{key} user variables" do
command "REG delete HKCU\\Environment /F /V #{key}"
only_if { registry_key_exists? "HKCU\\Environment\\#{key}" }
end
end
probably not the best way but it ran :)
I'm having troubles trying to understand what's going on, why pure ruby code is executed first despite that the code is put last, this is a part of what the action :install contains:
action :install do
...
windows_package "#{installation_name}" do
source "#{Chef::Config[:file_cache_path]}\\#{installer_filename}"
options "INSTALLDIR=\"#{installation_path}\""
action :install
not_if {::Dir.exists?("#{installation_path}\\bin")}
end
env "MYSQL_PATH" do
value "#{installation_path}"
end
windows_path "#{installation_path}\\bin" do
action :add
end
windows_batch "Installing Service" do
code <<-EOH
set MYSQL_PATH="#{installation_path}"
call %MYSQL_PATH%\\bin\\mysqld-nt.exe --install MySQL
EOH
end
service "MySQL" do
action :start
end
service "MySQL" do
action :enable
end
change_pass_str = "call \"#{installation_path}\\bin\\mysql.exe\" -u root --execute \"UPDATE mysql.user SET Password=PASSWORD('#{root_password}') WHERE User='root';FLUSH PRIVILEGES;\""
puts change_pass_str
password_set_result = system(change_pass_str)
log !password_set_result ? "Password wasn't changed since root already have a password defined. Maybe there's still data from a previous installation." : "Password has been set!"
end
Please ignore the fact that i didn't put the variable definition, and know that they are well defined. The thing is that when this part of the lwrp is executed
change_pass_str = "call \"#{installation_path}\\bin\\mysql.exe\" -u root --execute \"UPDATE mysql.user SET Password=PASSWORD('#{root_password}') WHERE User='root';FLUSH PRIVILEGES;\""
puts change_pass_str
password_set_result = system(change_pass_str)
it can't find #{installation_path}\\bin\\mysql.exe since it is not yet installed, in spite of that the block is at the end of the action.
Can anyone point me what is my mistake? why the other (already defined in this case in windows LWRP) resources are executed at the end instead of the begining?
How can i fix it?
LWRPs are no different than recipes with regards to execution order. So, just like in a recipe, any ruby code that is not inside a resource will be executed during the 'resource gathering' phase. In your case, you need to wrap your code in a ruby_block or execute resource like this:
ruby_block 'change the password' do
block {
change_pass_str = "call \"#{installation_path}\\bin\\mysql.exe\" -u root --execute \"UPDATE mysql.user SET Password=PASSWORD('#{root_password}') WHERE User='root';FLUSH PRIVILEGES;\""
puts change_pass_str
password_set_result = system(change_pass_str)
end
end
OR
execute'change the password' do
command "call \"#{installation_path}\\bin\\mysql.exe\" -u root --execute \"UPDATE mysql.user SET Password=PASSWORD('#{root_password}') WHERE User='root';FLUSH PRIVILEGES;\""
end
That will cause this code to evaluate at run-time. You'll also want to add an only_if, not_if, or action :nothing to that ruby_block to ensure it only runs when needed. Basically, a not_if or only_if would run SQL code to check if the password was set or not, and if that block returned false, then the password change wouldn't run.
Alternately, you can use action :nothing to set the resource to not run, and then use a notification or subscription to trigger the resource to run only after mysql is installed.
Chef has a two pass system see THIS
first pass 'compile' the recipes and add the resources inside a collection
second pass 'converge' and for each resource check if it needs to be updated and act consequently.
In your lwrp, the ruby in it is run as soon as being processed, include your code into a ruby_block resource (DOC) for it to be executed at the converge time.
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.