Check for existing directory fails in Ruby + Chef - ruby

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'

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

Changing directory in ubuntu with Ruby and a command line tool

#!/home/user/.rvm/rubies/ruby-2.0.0-p353/bin/ruby
case ARGV[0]
when "apache"
exec('cd /etc/apach2')
exec('sudo nano httpd.conf')
#...
end
I am trying to make a quick command line tool that will change directories for me with one word.. so from the command line (in ubuntu 12). It tells me it cant cd.. But I try the command myself and it will work just fine.
Ruby's Dir class is your friend for this, check-out the chdir method:
Dir.chdir('/path/to/change/to')
will change Ruby's concept of the current working directory for the time the code is running. Any sub-shells would consider that their starting directory.
You can also pass chdir a block, and all code in that block will assume the new directory, which will then revert to the old one when the block exits:
Dir.chdir('/path/to/change/to') do
# do some stuff
end
OK, so I did this and it works (I'm on OS X but should be the same):
ARGV[0]
when "testme"
system('cd ripple')
system('ls -al')
#...
end
calling system('cd ... does not change move you to that directory in the current shell you are executing your .rb file in. So it would make more sense to do:
system('sudo nano /etc/....
all on one line
I tested it with back ticks and it didn't work at all for me.
I tested with exec() and got the expected result, it runs one line and that's it. So exec() could work if you only have one command to run or you chain them all together with &&
exec('ls /etc && sudo nano /etc/....
I would read this: http://rubyquicktips.com/post/5862861056/execute-shell-commands

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.

How can I make a shell command execute synchronously in ruby

I want to extract zip files and delete them when they are extracted. Googling tells me that the easiest way to unzip files in Ruby is to execute them with unzip filename.zip. My next step is to delete the zip file.
The second step happens so fast that the shell command unzip does not have a chance to even see the file before it is deleted. It errors out saying
"unzip: cannot find either filename.zip or filename.zip.zip."
I just want to have the unzip... command complete before continuing execution of the ruby script. I want it to block synchronously. Is there a way to do that? I cannot use sleep because I cannot estimate how long it will take.
The usual ways to run an external program in ruby are synchronous so there should be no problem.
Try
`unzip`
or
system("unzip")
or
system("unzip x && rm x")

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