ruby_block conditional statement and waiting for reboot - ruby

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.

Related

Can opscode chef perform wait until a condition becomes true?

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

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'

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.

Pausing and resuming a Bash script

Is there a way to pause a Bash script, then resume it another time, such as after the computer has been rebooted?
The only way to do that AFAIK:
Save any variables, or other script context information in a temporary file to establish the state of the script just before the pause. This goes without saying that the script should include a mechanism to check this file to know if the previous execution was paused and, if it was, fetch all the context and resume accordingly.
After reboot, manually run the script again, OR, have the script automatically run from your startup profile script.
Try Ctrl-Z to pause the command. I don't think you can pause it and then resume after reboot unless you're keeping state somehow.
You can't pause and resume the same script after a reboot, but a script could arrange to have another script run at some later time. For example, it could create an init script (or a cron job, or a login script, etc) which contained the tasks you want to defer, and then removed itself.
Intriguing...
You can suspend a job in BASH with a CTRL-Z, but you can't resume after a reboot. A reboot initializes the machine and the process that was suspended is terminated.
However, it might be possible to force the process into a coredump via a 'kill -QUIT $pidand then usegdb` to restart the script. I tried for a while, but was unable to do it. Maybe someone else can point out the way.
If this applies to your script and the job it does, add checkpoints to it - that means places where all the state of the process is saved to disk before continuing. Then have each individual part check if the output they have to produce is already there, and skip running if it is. That should make a rerun of the script almost as efficient as resuming from the exact same place in execution.
Alternatively, run the script in a VM. Freeze the VM before shutting down the real system and resume it afterwards. It would probably take a really huge and complex shell script to make this worth it, though.

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