Run a command in the context of a different app - ruby-on-rails

I have two rails apps on the same server, let's call them A and B.
I am trying to have app A restart app B via app B's own capistrano task. Unfortunately, even after cd-ing to app B's directory, it is trying to run app A's capistrano instead. Am I missing something?
example code
system("cd /apps/appB/current && pwd && bundle exec cap:restart")
pwd correctly returns the path of appB (/apps/appB/current), however, in there is a traceback for cap:restart. This is because it is still trying to run the cap command in the context of appA, e.g.
/apps/appA/shared/bundle/ruby/1.9.1/gems/capistrano-2.15.4/lib/capistrano/configuration/loading.rb:152:in 'require': cannot load such file -- airbrake/capistrano (LoadError)
from /apps/appA/shared/bundle/ruby/1.9.1/gems/capistrano-2.15.4/lib/capistrano/configuration/loading.rb:152:in 'require'.
I tried without 'bundle exec', and have tried some other ways of making system calls. I also created a bash script in another directory and tried to run it that way.
All methods described exhibit the same behaviour.
Your help would be greatly appreciated =)

You need to use Bundler.with_clean_env to ensure that your subprocess doesn't pick up your current Bundler environment:
Bundler.with_clean_env do
system("cd /apps/appB/current && pwd && bundle exec cap:restart")
end
This is essentially the same problem as Install bundle of gem within other rails application

Since you said your apps are using Unicorn, you can signal from app A to app B (or the other way around).
Read this page: http://unicorn.bogomips.org/SIGNALS.html
The only thing each application needs to know is the pidfile path of the other application. So look at your Unicorn config and see where it's storing that.
You could either read the PID out of that pidfile and kill it from Ruby:
pid = File.read(path_to_other_application_pidfile).chop
Process.kill("USR2", pid)
Or you could use backticks to execute shell commands
`kill -s USR2 \`cat #{path_to_other_application_pidfile}\``

Related

Executing Rails 'whenever' gem when EC2 Instance Moves

I use the 'whenever' gem for my rails cron file in EC2 and it works great. "Whenever -w" writes it and I never have to worry about it again. The problem is when my instance has a planned reboot. The rails app get passed to a new instance and the whole process is seamless with no downtime, but the new instance does not have my cron file.
How can I make sure that the cron file gets written when I move to a new instance? Is there a way to run it on app start or something like that? Thanks.
Whenever is a command. If you run:
bundle exec whenever --help
You will get a full list of flags. You'll want:
bundle exec whenever -w /path/to/schedule.rb
You can add a simple shell script to /etc/init.d
#!/bin/bash
cd /to/app && /full/path/to/bundle exec whenever -w /full/path/to/schedule.rb

Running bundle from rails shell command

I have a Rails 4 application that executes several shell commands and everything works fine, but now I am trying to execute a shell command from it that checks the bundle of a different app or even an engine not used by this app, and all I get is the result as if it was checking its own bundle.
This is probably confusing so let me try to make it clearer:
Rails app
|--operations folder
|--app1
|--engine1
|--app2
now the rails app executes a shell command to check the bundle of any of those apps/engines in the operations folder like this:
out = %x[cd operations/app1 && bundle list 2>&1]
but the result is the list of gems used by the executing Rails app, not the list of gems from app1 that i want to check.
Why is that happening? I've also tried specifying the Gemfile using the --gemfile= option to no avail. how can I execute bundle operations on the target app?
The reason for this is that I have built a continuous integration application that tests and builds packages from our other apps and engines and sometimes some of the apps/engines require gems that the CI doesn't have, so running their tests fails and I wanted to make the CI install them before running the tests if it doesn't have them.
By default, child processes inherit the environment set up by Bundler.
To suppress this behaviour, call Bundler.with_clean_env with a block that contains the commands you want to run in the clean environment:
out = Bundler.with_clean_env { %x[cd operations/app1 && bundle list 2>&1] }
For more on this, see http://bundler.io/man/bundle-exec.1.html#Shelling-out

Running Rails Task From Cron

I have a Rails runner task that I want to run from cron, but of course cron runs as root and so the environment is set up improperly to get RVM to work properly. I've tried a number of things and none have worked thus far. The crontab entry is:
* 0 * * * root cd /home/deploy/rails_apps/supercharger/current/ && /usr/local/rvm/wrappers/ruby-1.9.3-p484/ruby bundle exec rails runner -e production "Charger.start"
Apologies for the super long command line. Anyhow, the error I'm getting from this is:
ruby: No such file or directory -- bundle (LoadError)
So ruby is being found in the RVM directory, but again, the environment is wrong.
I tried rvm alias delete [alias_name] and it seemed to do something, but darn if I know where the wrapper it generated went. I looked in /usr/local/rvm/wrappers and didn't see one with the name I had specified.
This seems like a common problem -- common enough that the whenever gem exists. The runner command I'm using is so simple, it seemed like a slam dunk to just put this entry in the crontab and go, but not so much...
Any help with this is appreciated.
It sounds like you could use a third-party tool to tether your Rails app to cron: Whenever. You already know about it, but it seems you never tried it. This gem includes a simple DSL that could be applied in your case like:
every :day # Or specify another period, or something else, see README
runner "Charger.start"
end
Once you've defined your schedule, you'll need to write it into crontab with whenever command line utility. See README file and whenever --help for details.
It should not cause any performance impact at runtime since all it does is conversion into crontab format upon deployment or explicit command. It's not needed, once the server is running, everything is done by cron after that.
If you don't want an extra gem anyway, you might as well check what command does it issue for executing your task. Still, an automated way of adding a cron task is easier to maintain and to deploy. Sure, just tossing a line into the crontab is easier — just for you and just this once. Then it starts to get repetitive and tiring, not to mention confusion for other potential developers who will have to set up something similar on their own machines.
You can run cron as different user than root. Even in your example the task begins with
* 0 * * * root cd
root is the user that runs the command. You can edit it with crontab -e -u username.
If you insist on running cron task as root or running as other user does not work for some reason, you can switch user with su. For example:
su - username -c "bundle exec rails runner -e production "Charger.start"

Run Rails commands outside of console

With my large application, the Rails console takes a while to load up. Is there a way to single commands more easily?
I'd also like to be able to automate stuff, and echo "query" | rails console isn't a great way to do things.
Thoughts?
EDIT: What about a long-running process that I can ping queries to whenever I have need?
There are two main ways to run commands outside console:
Rake task which depends on :environment
rails runner (previously script/runner), eg:
$ rails runner "query"
Both are pretty well documented on the rails guide: https://guides.rubyonrails.org/command_line.html#bin-rails-runner
Both of these methods will still take the same time as a console to fire up, but they are useful for non-interactive tasks.
Just pipe it in:
echo 'puts Article.count' | bundle exec rails c
It should now be a lot faster than when then the question was originally asked, because of Spring. It's not immediate, but still a lot faster than spinning up the whole app. Use this for the fast lane, it should run in under a second (assuming your required command is fast):
echo 'puts Article.count' | spring rails c
If you really want a single long-running process, you could easily do it by creating a controller action that simply runs whatever you POST to it, then send commands to it using curl behind an alias. The action would of course be completely insecure and should be triple-guarded against running anywhere near production, but it would be easy to setup.
Solution: bundle exec command allows us to run an executable script in the specific context of the project's bundle - making all gems specified in the Gemfile available to require in Ruby application. In addition it eventually avoids any conflicts with other versions of rake installed globally.
echo '<command>' | bundle exec rails c
for more information look at the documentation of bundler
example:
configuration_item=$(echo 'ConfigurationManager.getKey("authentication_method")' | bundle exec rails c )
echo $configuration_item
#output:
MFA_authentication

How to execute a command on the server with Capistrano?

I have a very simple task called update_feeds:
desc "Update feeds"
task :update_feeds do
run "cd #{release_path}"
run "script/console production"
run "FeedEntry.update_all"
end
Whenever I try to run this task, I get the following message:
[out :: mysite.com] sh: script/console: No such file or directory
I figured it's because I am not in the right directory, but trying
run "cd ~/user/mysite.com/current"
instead of
run "cd #{release_path}"
Also fails. Running the exact same commands manually (through ssh) works perfectly.
Why can't capistrano properly cd (change directory) into the site directory to run the command?
Thanks!
Update: Picked an answer, and thank you so much to all who replied.
The best answer may actually be the one on server fault, though the gist of both (the one on server fault and the one on stack overflow) is the same.
You want to use script/runner. It starts an instance of the app to execute the method you want to call. Its slow though as it has to load all of your rails app.
~/user/mysite.com/current/script/runner -e production FeedEntry.update_all 2>&1
You can run that from the capistrano task.
I cannot imagine that you would be able to remotely log into rails console from capistrano. I suggest you call your model method from a rake task.
How do I run a rake task from Capistrano?
As for the latter part of your question, are you logging into the server with the same user account as capistrano?

Resources