I am deploying a Rails application using AWS OpsWorks. To precompile the assets, I use the following Chef recipe:
node[:deploy].each do |application, deploy|
deploy_to = node[:deploy][application][:deploy_to]
rails_env = node[:deploy][application][:rails_env]
directory "#{deploy_to}/shared/assets"
link "#{deploy_to}/current/public/assets" do
to "#{deploy_to}/shared/assets"
end
execute "rake assets:precompile" do
cwd "#{deploy_to}/current"
command "bundle exec rake assets:precompile"
environment "RAILS_ENV" => rails_env
end
end
It precompiles correctly, but in following deployments it goes through the whole precompile process again, even though no asset was modified and the assets folder is shared. I also tried a Chef hook, as suggested here, with the same result. How could you make it run only when needed?
You can add not_if or only_if clause to the statement:
Something like this:
execute "rake assets:precompile" do
cwd "#{deploy_to}/current"
command "bundle exec rake assets:precompile"
environment "RAILS_ENV" => rails_env
not_if { File.exists?("<path to expected precompiled asset>") }
end
If you want it to run every time there is a change in a certain directory, you can use chef notifications.
Related
Background Info: I have made a Ruby on Rails application that utilizes the ruby gem Devise. When using the gem I have configured the gem's secret key in OpsWorks console as an environment variable.
When running the before_migrate.rb deployment hook I run the command rake assets:precompile. But I receive an error in the deployment log.
execute "rake assets:precompile" do
cwd release_path
command "bundle exec rake assets:precompile"
environment "RAILS_ENV" => rails_env
end
Error executing action run on resource 'execute[rake
assets:precompile]'
Mixlib::ShellOut::ShellCommandFailed
Expected process to exit with [0], but received '1'
---- Begin output of bundle exec rake assets:precompile --trace ---- STDOUT: STDERR:
** Invoke assets:precompile (first_time)
** Invoke assets:environment (first_time)
** Execute assets:environment
** Invoke environment (first_time)
** Execute environment rake aborted! Devise.secret_key was not set. Please add the following to your Devise initializer:
config.secret_key = '*****'
I know the environment variables are eventually being set because in my before_migrate.rb file I output the environment variables to log. When I hard code the config.secret_key everything works correctly.
Question: When using OpsWorks do the initializers config/initializers/* from my project get run before any of the environment variables get set? Is there another way around this without hard coding keys or SSHing into every instance?
You would be able to access the environment variable in the chef recipe by,
node[:deploy]['<YOUR_APP_SHORT_NAME>'][:environment_variables][:SECRET_KEY_BASE]
So you can write a recipe to put secrets.yml or something.yml with the environment value.
Create a template,
your_cookbook/templates/default/secret.yml.erb
secret_key: <%= node[:deploy]['<YOUR_APP_SHORT_NAME>'][:environment_variables][:SECRET_KEY_BASE] %>
You can place the secret.yml in /data/YOUR_APPLICATION_SLUG_NAME/shared/config by a recipe.
your_cookbook/recipes/default/secret.rb
template "/data/#{<YOUR_APP_SLUG_NAME}/shared/config/secrets.yml" do
source "secrets.yml.erb"
owner 'OWNER'
end
And make sure that you are symlinking the secrets.yml in shared/config folder to your release path in before_migrate.rb in your deploy hooks.
your_code_path/deploy/before_migrate.rb
run "ln -nfs /data/<YOUR_APP_SLUG_NAME>/shared/config/secrets.yml #{release_path}/config/secrets.yml"
And have the recipe your_cookbook::secret in your deploy life cycle event.
Now you would have generated secrets.yml in your code path/config. You have to load that yaml and set it in config/initializer/devise.rb
config.secret_key = <SECRET KEY FROM THE YML GENERATED>
What's the difference between adding a RAILS_ENV before or after a rake task? Here are samples from my staging environments:
Adding RAILS_ENV after rake task.
This raised an error, and the reason for this is accepting development environment as by default and not taking devutility as the environment.
$bundle exec rake -T RAILS_ENV=devutility
$rake aborted!
$cannot load such file -- rack/bug
Adding RAILS_ENV before rake task
This works and lists all the rake task available.
$RAILS_ENV=devutility bundle exec rake -T
rake about # List versions of all Rails frameworks and the environment
rake assets:clean # Remove compiled assets
rake assets:precompile # Compile all the assets named in config.assets.precompile
rake bourbon:install[sass_path] # Move files to the Rails assets directory
rake ci # Continuous Integration build (simplecov-rcov and deploy)
rake cucumber # Alias for cucumber:ok
rake cucumber:all # Run all features
rake cucumber:ok # Run features that should pass
rake cucumber:rerun # Record failing features and run only them if any exist
rake cucumber:wip # Run features that are being worked on
rake db:create # Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)
rake db:data:dump ....................
..............
RAILS_ENV is an environment variable that needs to be available before running your rake task.
When you do:
RAILS_ENV=devutility bundle exec rake -T
It has the same affect as:
export RAILS_ENV=devutility
bundle exec rake -T
RAILS_ENV is not an argument to rake as it may appear, it's part of the environment available to Ruby though it's ENV constant.
I am using Linode with Ubuntu 10.04 and Capistrano, Unicorn, & Nginx to deploy.
How do I do the equivalent of heroku run rake db:reset with this setup? Is it as simple as cap deploy:cold again to run the migrations?
I've already deployed and want to drop all databases and rerun all the migrations but am not sure which commands to run with this setup to do so.
I wrote a tiny little file you can copy to run arbitrary rake tasks via capistrano: http://jessewolgamott.com/blog/2012/09/10/the-one-where-you-run-rake-commands-with-capistrano/
once setup, you can:
cap sake:invoke task="db:reset"
For Capistrano 3 without actual dropping the database. Use bundle exec cap db:reset
namespace :db do
desc 'Resets DB without create/drop'
task :reset do
on primary :db do
within release_path do
with rails_env: fetch(:stage) do
execute :rake, 'db:schema:load'
execute :rake, 'db:seed'
end
end
end
end
end
You could add the following to your deploy.rb file
namespace :custom do
task :task do
run "cd #{current_path} && bundle exec rake db:reset RAILS_ENV=#{rails_env}"
end
end
Then run cap custom:task to clear the database.
If you are using Capistrano 3, consider using the capistrano-rails-collection.
You can also use copy the code directly from db.rake file from the repository.
Or, if you want a full-fledged solution to run all your rake tasks on a remote server, check out the Cape gem.
I've been doing Ruby on Rails development with ElasticSearch between two machines and its starting to get a little annoying. My usual workflow is:
git pull
bundle install
rake db:migrate (or rake db:setup depending)
rails server
elasticsearch -f -D myconfig.xml
rake environment tire:import CLASS=MyObject FORCE=true
Is there anyway I can add all of these commands to some type of start up script in Rails to bring them all into one place? It would make bringing up a dev environment a lot easier on me everytime I switch machines.
The best way I've found is to use the Foreman gem to kickstart your project and associated processes.
It looks like you should do this in your deployment using Capistrano. Here is an example config/deploy.rb file:
[basic parts omitted]
after "deploy", "bundler:bundle_install"
after "bundler:bundle_install", "db:db_migrate"
after "deploy:db_migrate", "deploy:elastic_search_indexing"
namespace :bundler do
desc 'call bundle install'
task :bundle_install do
run "cd #{deploy_to}/current && bundle install"
end
end
namespace :db do
desc 'fire the db migrations'
task :db_migrate do
run "cd #{deploy_to}/current && bundle exec rake db:migrate RAILS_ENV=\"production\""
end
end
namespace :elasticsearch do
desc 'run elasticsearch indexing via tire'
task :index_classes do
run "cd #{deploy_to}/current && bundle exec rake environment tire:import CLASS=YourObject FORCE=true "
end
end
[rest omitted]
Make sure you have a config file on the target machine (Linux) in /etc/elasticsearch/elasticsearch.yml with contents like:
cluster:
name: elasticsearch_server
network:
host: 66.98.23.12
And the last point to mention is that you should create an initializer config/initializers/tire.rb:
if Rails.env == 'production'
Tire.configure do
url "http://66.98.23.12:9200"
end
end
As you can see, this is the exact same IP address, but only used for the production environment. I assume that you access elasticsearch locally (in development mode) via localhost. elasticsearch is connection per default to
http://0.0.0.0:9200
A good starting point and also in depth help is provided by awesome Ryan Bates and his Railscasts http://railscasts.com/episodes?utf8=%E2%9C%93&search=capistrano
Whats keeping you from putting it in a bash script? And put the script inside your RAILS_APP_HOME/scripts folder?
#!/bin/sh
git pull
bundle install
rake db:migrate
rails server
elasticsearch -f -D myconfig.xml
rake environment tire:import CLASS=MyObject FORCE=true
I have a rails app that is not in the root directory of the repository. When it is deployed, some other static files are deployed with it in a parent directory. The structure is something like this:
root
-- otherstuff
-- railsapp
When I do a deployment with cap deploy:migrations, the Capistrano command that gets executed looks like this, which of course doesn't work:
cd /u/apps/minicart/releases/20100717215044; rake RAILS_ENV=staging db:migrate
How do I change this so that it will be:
cd /u/apps/minicart/releases/20100717215044/railsapp; rake RAILS_ENV=staging db:migrate
I made it work by adding a task that executes this command after deploy:finalize_update, but I would prefer to use the built in method, plus my hacked version is executed with every deployment.
Any advice would be appreciated.
Tim
This turned out to be very simple.
I added a deploy namepace to my deploy.rb file and then redefined the migrate method. Now my method runs on cap deploy:migrations.
namespace :deploy do
desc "Migrating the database"
task :migrate, :roles => :app do
run <<-CMD
cd #{release_path}/minicart; RAILS_ENV=#{stage} rake db:migrate
CMD
end
end