this is probably a newbie ruby question. I have several libraries and apps that I need to deploy to several different hosts. All of the apps and libs will share some common settings for those hosts-- e.g. host name, database server/user/pass, etc.
My goal is to do something like:
cap host1 stage deploy
cap host2 stage deploy
cap host1 prod deploy
# ...
My question is how do you include these common settings in all of your deploy.rb files? More specifically, I want to create a an rb file that I can include that has some common settings and several host specific task definitions:
set :use_sudo, false
# set some other options
task :host1 do
role :app, "host1.example.com"
role :web, "host1.example.com"
role :db, "host1.example.com", :primary => true
set :rodb_host, "dbhost"
set :rodb_user, "user"
set :rodb_pass, "pass"
set :rodb_name, "db"
end
task :host2 do
#...
end
deploy.task :carsala do
transaction do
setup
update_code
symlink
end
end
And then "include" this file in all of my deploy.rb files where I define stage, prod, etc and overwrite any "common" configuration parameters as necessary. Any suggestions would be appreciated. I've tried a few different things, but I get errors from cap for all of them.
Edit: I've tried
require 'my_module'
But I get errors complaining about an undefined task object.
I just experimented with it a little more and what I discovered is that you have to:
load 'config/my_module'
I can put all of my common definitions here and just load it into my deploy.rb.
It appears from the docs that load loads and executes the file. Alternatively, require attempts to load the library specified. I'm not totally sure about real difference, but it appears that there is some separation between the current app symbol space and the library require'd (hence the errors about the undefined task object) that isn't a problem when you do a load.
require 'my_extension'
Save your extensions in my_extension.rb
Jon has it right, that's the simplest way to go, just save it in a separate file and use require 'filename'. You could also use something fancy like Webistrano for deployment which also supports this in the form of Capistrano 'Recipes'. I've been using it for a while on a few projects and have come to love it.
I'm not sure how complex your needs are, but this works well for me for deployment:
set :application, "app"
set :scm, :subversion
# ... set all your common variables
task :staging do
set :repository, "http://app/repository/trunk/"
# ... set other uncommon variables in task
end
task :production do
set :repository, "http://app/repository/production/"
# ...
end
Deployment is just
cap staging deploy
or
cap production deploy
Related
I'm working on a capistrano deployment configuration and would like to set the shared folder on another place. Background is, that I want to use a wildcard deployment (review app) and the target directory will be generated on-the-fly (which means, there isn't a shared folder in it) and I would use the shared folder with the assets across ALL review apps in this environment.
Therefore I have directories on the server:
/var/www/review/application_name
/var/www/review/application_name/shared/... (here are the assets and configurations I would like to share across ALL review apps)
/var/www/review/application_name/branch-name/ - this is the deployment path which will be created by capistrano when deploying a specific branch to the review stage.
I have used shared_path
set :shared_path, "/var/www/review/#{fetch(:application)}"
which works fine for the linked_dirs, but NOT for the linked_files. I get the error message:
00:01 deploy:check:linked_files
ERROR linked file /var/www/review/www.app.tld/123/shared/myfile does not exist on review.app.tld
which is true - but I don't know how to tell cap to put it in place. Of course the named file is in the shared folder
/var/www/review/www.app.tld/shared/
but capistrano seems to search on the wrong place when trying to check the linked_files (again: the linked_dirs are processed correct).
Any hints? Thanks in advance!
The shared_path is not something you can configure directly. Using set will not have any effect.
The shared path in Capistrano is always a directory named shared inside your :deploy_to location. Therefore if you want to change the shared path, you must set :deploy_to, like so:
set :deploy_to, -> { "/var/www/review/#{fetch(:application)}" }
This will effectively cause shared_path to become:
"/var/www/review/#{fetch(:application)}/shared"
Keep in mind that :deploy_to is used as the base directory for many things: releases, repo, current, etc. So if you change :deploy_to you will affect all of them.
If your :application variable is defined at some later point, or changed, you'll need to set to a deferred variable:
set :shared_path, -> { "/var/www/review/#{fetch(:application)}" }
This evaluates that string on-demand instead of in advance.
I trying to run capistrano v.3 task in same stage with diferent ssh_options.
my production.rb say:
set :stage, :production
set :user, 'deploy'
set :ssh_options, { user: 'deploy' }
With this configuration capistrano connect with user deploy which is correct for the rest of taks. But I need connect it for one specific task with an_other_user wich is well configured in server.
Then my recipe say:
...
tasks with original user
...
task :my_task_with_an_other_user do
set :user, 'an_other_user'
set :ssh_options, { user: 'an_other_user' }
on roles(:all) do |host|
execute :mkdir, '-p', 'mydir'
end
end
...
other tasks with original user
...
When execute:
cap production namespace:my_task_with_an_other_user
capistrano make ssh conexion with original :user "deploy" (the user declared in production.rb).
How can I change the user and/or ssh_options it inside task?
Capistrano 3
I had hard time finding out a solution. But the solution is much nicer than version 2. Cap team has done a great job. Make sure you have updated Capistrano to 3.2.x+ Here's the trick:
# in config/deploy/production.rb, or config/deploy/staging.rb
# These roles are used for deployment that works with Cap hooks
role :app, %w{deploy#myserver.com}
role :web, %w{deploy#myserver.com}
role :db, %w{deploy#myserver.com}
# Use additional roles to run side job out side Capistrano hooks
# 'foo' is another ssh user for none-release purpose tasks (mostly root tasks).
# e.g. user 'deploy' does not have root permission, but 'foo' has root permission.
# 'no_release' flag is important to flag this user will skip some standard hooks
# (e.g. scm/git/svn checkout )
role :foo_role, %w{foo#myserver.com}, no_release: true
Make sure both 'deploy' & 'foo' user can ssh into the box. Within your tasks, use the on keyword:
task :restart do
on roles(:foo_role) do
sudo "service nginx restart"
end
end
task :other_standard_deployment_tasks do
on release_roles(:all) do
# ...
end
end
Other gotchas:
Make sure some Capistrano tasks skips the additional no release role you added. Otherwise, it might cause file permission issues during deployment. E.g. capistrano/bundler extension need to override the default bundler_roles
set :bundler_roles, %w(web app db) # excludes the no release role.
Read more about no_release flag: related Github issue.
Capistrano 2
I used to have a custom functions to close and reconnect ssh sessions.
Reference Paul Gross's Blog
Place the following methods in deploy.rb. Call with_user to switch ssh session. Slightly simplified version:
def with_user(new_user, &block)
old_user = user
set :user, new_user
close_sessions
yield
set :user, old_user
close_sessions
end
def close_sessions
sessions.values.each { |session| session.close }
sessions.clear
end
Usage:
task :update_nginx_config, :roles => :app do
with_user "root" do
sudo "nginx -s reload"
end
end
Answer of #activars didn't work for me. Because when I defined several roles for one environment - only one was deployed :(
So my solution was to create several enviroments, e.g. production.rb and productionroot.rb.
productionroot has content with no_release=true flag - just as you've specified:
server '146.120.89.81', user: 'root', roles: %w{foo_role}, no_release: true
After that I've created sh script which runs
#/usr/bin/env bash
bundle exec cap production deploy
bundle exec cap productionroot deploy
I'm trying to deploy my application with the multistage/ext estensions. It works fine when each stage is in different servers. But for one particular proyect I want to deploy two stages to the same server.
For that I need to set a different application name on each server. And that is what isn't working.
I moved the application variable to the stage file and define the deploy_to variable with lazy loading.
config/deploy.rb
set :stages, %w(production beta)
set :default_stage, "beta"
require 'capistrano/ext/multistage'
#set :application, "myapp-beta"
set :user, "deploy"
set (:deploy_to) { "/home/#{user}/applications/#{application}" }
config/deploy/beta.rb
server "my.server.com", :web, :app, :db, primary: true
set :application, "myapp-beta"
set :domains, "beta.myapp.com"
set :branch, "beta"
I get Please specify the name of your application, set :application, 'foo' error
Maybe I should be thinking about deploying to a different server, but now I really want to understand why this isn't working.
thanks
There is nothing functionally wrong with your code. As Lavixu mentioned, as long as you specify the stage name when deploying (i.e. cap beta deploy:setup, cap beta deploy) then the application variable will be recognised.
set (:deploy_to) { "/home/#{user}/applications/#{application}" }
Should be
set :deploy_to, -> { "/home/#{user}/applications/#{application}" }
I had to use fetch to reference other capistrano variables. ie:
set :deploy_to, -> { "/var/deploy/#{fetch(:application)}" }
Trying to access application directly raised a undefined local variable or method 'application' for main:Object error.
My database.yml looks like:
adapter: mysql
database: my_db
username: user1
password: '123'
host: localhost
This is a non-rails application, just using rake/ruby for some scripting.
Can I set a default (dev) and production in this yaml file, or is that rails specific?
If yes, when running something like:
rake user:create
How do I pass in if it is production and therefore use the production db settings in the yaml file?
Where you read the yaml file into memory and parse it is a good place to put the logic to use the current environment (or a default if none is set). A simple way to do this is to rely on an environment variable (don't use RAILS_ENV if it's not a rails app, or RACK_ENV if it's not a rack app).
Use something like:
my_env = ENV['MY_ENV_VARIABLE'] || 'development'
db_settings = YAML::load(File.open(yml_file))[my_env]
Then you can call rake via:
MY_ENV_VARIABLE=production rake my_task
Or if you want to add a param to the task itself, you can set it up to use
rake my_task[production]
(but that can get messy depending on if you have to quote the whole thing, like in zsh).
Another approach that some libraries use (like heroku_san) is to have a separate task that sets the environment variable, and rely on calling multiple tasks, so you'd have a task :production that sets the environment variable and can then call
rake production my_task
I have rake tasks for getting the production database from the remote server, etc. It's always the same tasks but the server info changes per project. I have the code here: https://gist.github.com/868423 In the last task, I'm getting a #local_db_dir_path = nil error.
I don't think want to use shell environment variables because I don't want to set them up each time I use rake or open a new shell.
Stick the settings in a YAML file, and read it like this:
require 'yaml'
config = YAML.load("config.yaml") # or wherever
$remote_host = config['remote_host']
$ssh_username = config['ssh_username']
# and so on
Or you can just read one big config hash:
$config = YAML.load("config.yaml")
Note that I'm using globals here, not instance variables, so there's no chance of being surprised by variable scope.
config.yaml would then look like this:
---
remote_host: some.host.name
ssh_username: myusername
other_setting: foo
whatever: bar
I tend to keep a config.yaml.sample checked in with the main body of the code which has example but non-working settings for everything which I can copy across to the non-versioned config.yaml. Some people like to keep their config.yaml checked in to a live branch on the server itself, so that it's versioned, but I've never bothered with that.
you should be using capistrano for this, you could use mulitsage or just separate host setting to a task, example capistrano would look like this:
task :development do
server "development.host"
end
task :backup do
run "cd #{current_path}; rake db:dump"
download "remote_path", "local_path"
end
and call it like this:
cap development backup