Can opscode chef perform wait until a condition becomes true? - ruby

we have a use case where we want the chef orchestration to wait until a particular directory gets deleted in the machine. is there some way to achieve it?
I searched in the internet and found the following cookbook
I feel it can be used but i am having difficulty in understanding how can I use it, there is no read me about using it.
How can i achieve it?
edit to remove hold:
say you have following recipe
execute 'making dir' do
command 'mkdir /tmp/test2'
not_if do ::File.directory?('/tmp/test1') end
end
reference: https://docs.chef.io/resource_common.html#not-if-examples
here, what i want by not_if was "wait until /tmp/test1 gets deleted" but how chef executs this is like "it found directory exixting so it did not execute the resource and exited"
i need a way to perform wait until a condition becomes true.

It's actually a pattern I've seen from time to time in various cookbooks, often used to wait for a block device or to mount something, or to wait for a cloud resource. For the wait cookbook you've found, I had to dig up the actual source repo on Github to figure out how to use it. Here's an example:
until 'pigs fly' do
command '/bin/false'
wait_interval 5
message 'sleeping for 5 seconds and retrying'
action :run
end
It appears to call ruby's system(command) and sleep(wait_interval). Hope this helps!
EDIT: As other posters have stated, if you can do the whole thing in chef, a notification with a directory resource and delete action is a better solution. But you asked how to use the wait resource, so I wanted to answer that specifically.

First off, don't shell out to create a directory. You aren't gaining much other just writing a shell script if you only use Chef to exec shell commands. It is much better to rely on the Chef directory resource to do that for you. Then, you can be confident that it will work every time on every system. Plus, you will be able to make use of the the Chef resource that represents your directory so that you can do things such as notifications.
Here is a slight refactoring of both directory operations:
# Ensure that your directory gets deleted, if it exists.
directory '/tmp/test1' do
action :delete
notifies :action, 'directory[other_dir]', :immediately
end
# Define a resource for your directory, but don't actually do anything to the underlying machine for now.
directory 'other_dir' do
action :nothing
path '/tmp/test2'
end

Related

ruby_block conditional statement and waiting for reboot

I got some great direction from coderanger on chef IRC and I didn't want to bug him further but here's my problem:
My goal is to run recipe one, then two, then reboot while keeping the chef run alive, then execute recipe three. I think I'm close but still running into errors. I'm not sure if it's my code block or the way Windows behaves.
Here's my code block:
ruby_block "test" do
block do
run_context.include_recipe "stuff::reboot"
while reboot_pending? == true do
run_context.include_recipe "stuff::three"
end
end
end
The block executes with no issue but it seems to move on quickly to the stuff::three recipe which causes this error because powershell isn't available since the machine is still booting up:
==> default: The argument 'c:/tmp/vagrant-elevated-shell.ps1' to the -File parameter does not exist. Provide the path to an existing '.ps1' file as an argument to the -File parameter.
I'm thinking that once the reboot command is issued, after a few seconds there is no longer a pending reboot. So, is there a different ruby helper to use my while with? Or is this block just crap?
Sooooo I answer a lot of the questions here too. Thanks for the thought though.
You keep trying to put the include_recipe in the body of the while loop, which isn't what you want I don't think. That says "include this over and over while a reboot is pending".
What you want is this:
include_recipe 'stuff::reboot'
ruby_block 'wait for reboot' do
block do
true while reboot_pending?
end
end
include_recipe 'stuff::three'
That will put a resource in the collection between the stuff from the two recipes that halts the converge if a reboot is pending, otherwise it continues.

Check for existing directory fails in Ruby + Chef

This is my piece of Ruby in a Chef recipe:
# if datadir doesn't exist, move over the default one
if !File.exist?("/vol/postgres/data")
execute "mv /var/lib/postgresql/9.1/main /vol/postgres/data"
end
The result is:
Executing mv /var/lib/postgresql/9.1/main /vol/postgres/data
mv: inter-device move failed: `/var/lib/postgresql/9.1/main' to `/vol/postgres/data/main'; unable to remove target: Is a directory
I know that /vol/postgres/data exists and is a directory, yet it still attempts to execute the mv. Why?
Just to be sure, running the following standalone Ruby script on the same machine outputs "nomv":
if !File.exist?("/vol/postgres/data")
print "mv"
else
print "nomv"
end
I was not so attentive earlier, I thought you are checking for file existence in not_if or only_if block. Your problem is similar to the one in this question: Chef LWRP - defs/resources execution order. See the detailed explanation there.
Your problem is that !File.exist?("/vol/postgres/data") code gets executed straight away - (because it's pure ruby), before any resource is executed and thus before the postgress is installed.
The solution should be to move the check to not_if block.
execute "mv /var/lib/postgresql/9.1/main /vol/postgres/data" do
not_if { File.exist?("/vol/postgres/data") }
end
Use this block of code :
execute "name" do
command "mv /var/lib/postgresql/9.1/main /vol/postgres/data"
not_if { ::File.exists?("/vol/postgres/data")}
end
OR
you can also use
execute "name" do
command "mv /var/lib/postgresql/9.1/main /vol/postgres/data"
creates "/vol/postgres/data"
end
Both will run the command only if /vol/postgres/data is not present in the file system.
If you want to run block of commands then use something like this,
bash 'name' do
not_if { ::File.exists?("/vol/postgres/data") }
cwd "/"
code <<-EOH
mv /var/lib/postgresql/9.1/main /vol/postgres/data
#any other bash commands
#any other bash commands
EOH
end
I use
!::File.directory?(::File.join('path/to/directory', 'directory_name'))
To test if a directory exists you can use an equvivalent of File.exists which is Dir.exist:
Dir.exist?("/vol/postgres/data")
As others pointed out, you should use not_if or only_if instead of using plain Ruby condition, so I'm not going to explain it again. Check Draco's answer for details.
execute "mv /var/lib/postgresql/9.1/main /vol/postgres/data" do
not_if { Dir.exist?("/vol/postgres/data") }
end
I would use, !File.directory?("/vol/postgres/data")
Are you calling it within your rails application or it is a standalone ruby file.
If you are doing in your rails app.
Then,
File.exist?("#{Rails.root}/ur-file-path")
Ex: File.exist?("#{Rails.root}/public/ur-filename")
You need to specify the particular file path from root.
A quick google search turns up a lot of answers regarding "inter-device move failed". Ruby is just passing along the error returned by the operating system; this has nothing to do with testing the file as the other answers indicate.
From: http://insanelabs.com/linux/linux-cannot-move-folders-inter-device-move-failed-unable-to-remove-target-is-a-directory/
This is somewhat simple as long as we understand the concept. mv or move does not actually move the file/folder to another location within the same device, it merely replaces the pointer in the first sector of your device. The pointer (in inode table) will be moved, but nothing is actually being copied. This will work as long as you stay within the same media/device.
Now, when you try to move files from one device to another (/dev/sda1 to /dev/sdb1) you will run into “inter-device move failed, unable to remove target: Is a directory” error. This happens when mv has to actually move your data to another device, but cannot remove the inode/pointer, because if it did then there will be no data to fall back to, and if it didn’t then mv operation is not really complete because we will end up with data in source. Damned if you do and damned if you don’t, so it’s wise not to do it to begin with!
In such situation cp is best. Copy your data over and then remove your source manually.
A better solution might be to just use ruby tools instead of executing a shell command, since it says If file and dest exist on the different disk partition, the file is copied then the original file is removed.
FileUtils.mv '/var/lib/postgresql/9.1/main', '/vol/postgres/data'

Changing directory for User in Ruby script

I'm quite familiar with Dir.chdir("/xyz")
Unfortunately, this changes the directory of the process, but not actually the directory of the user. I'll make the following example to illustrate my need.
$~/: ruby my_script.rb
CHANGING TO PATH FOR USER NOT SCRIPT
$/Projects/Important/Path: pwd
$/Projects/Important/Path
See? I need the script to change the user's path. Performing system/backticks/Dir.chdir all adjust the process path, and end with the user sitting where they started, instead of the path I want them.
From what I've read exec was the way to go, since it takes over the existing process... but to no avail.
You can't, but you can do something which might be good enough. You can invoke another shell from ruby:
Dir.chdir("/xyz")
system("bash")
Running this will create a new bash process, which will start in the /xyz directory. The downside is that changing this process will bring you back to the ruby script, and assuming it ends right away - back to the bash process that started the ruby script.
Another hack that might work is to use the prompt as a hackish hook that will be called after each command. In the ruby script, you can write the new directory's path somewhere that can be read from both bash and ruby(for example a file - but not an environment variable!). In the PROMPT_COMMAND function, you check that file and cd to what's written there. Just make sure you delete that file, so you don't get automatically cded there after every command you run.

Convert Chef recipe to series of Bash commands?

Typically, one wants to convert Bash scripts to Chef. But sometimes (like, right now) you need to do the opposite. Is there an automatic way to get the list of commands run for a given Chef cookbook on a given configuration?
I'm not trying to end up with something with the full functionality of the Chef cookbook. I want to end up with a small set of commands that reproduce this particular installation on this particular environment. (The reason in this case is I need to separate out the 'sudo' commands and get them run by someone else. I do have sudo access on a machine that I could run Chef on to carry out this task though.)
I doubt you can do that in general, and even if you could, it would likely be more work than implementing what you need in Chef.
For example, even something as simple as creating a configuration file is implemented in Chef as ruby code; you would need to figure out a way to turn that into echo "…" > /etc/whatever.com. Doing that for all resources would be a major undertaking.
It seems to me that what you should actually do is modify any Chef cookbook that you use to run commands as a different user.
Things like template and file are pretty easy: the file will be created as root, and then chown-ed to the correct user. execute resources (which run commands) can be configured to run the command with su simply by specifying the user:
execute "something" do
command "whoami"
user "nobody"
end
It might take you a while to figure out, but once you get the hang of it it's pretty easy; much easier than converting to bash.

Determine if a ruby script is already running

Is there an easy way to tell if a ruby script is already running and then handle it appropriately? For example: I have a script called really_long_script.rb. I have it cronned to run every 5 minutes. When it runs, I want to see if the previous run is still running and then stop the execution of the second script. Any ideas?
The ps is a really poor way of doing that and probably open to race conditions.
The traditional Unix/Linux way would be to write the PID to a file (typically in /var/run) and check to see if that file exists on startup.
e.g. the pidfile being located at /var/run/myscript.pid then you'd check to see if that exists before running the program. There are a few tricks to avoid race conditions involving using O_EXCL (exclusing locking) to open the file and symbolic links.
However unlikely, you should try to code to avoid race conditions by using atomic operations on the filesystem.
To save re-inventing the wheel, you might want to look at http://rubyforge.org/projects/pidify/
Highlander
Description
A gem that ensures only one instance of your main script is running.
In short, there can be only one.
Installation
gem install highlander
Synopsis
require 'highlander' # This should be the -first- thing in your code.
# Your code here
Meanwhile, back on the command line...
# First attempt, works. Assume it's running in the background.
ruby your_script.rb
# Second attempt while the first instance is still running, fails.
ruby your_script.rb # => RuntimeError
Notes
Simply requiring the highlander gem ensures that only one instance
of that script cannot be started again. If you try to start it again
it will raise a RuntimeError.
You should probably also check that the process is actually running, so that if your script dies without cleaning itself up, it will run the next time rather than simply checking that
/var/run/foo.pid exists and exiting.
In bash:
if ps aux | grep really_long_script.rb | grep -vq grep
then
echo Script already running
else
ruby really_long_script.rb
fi

Resources