Creating a Capistrano task that performs different tasks based on role - ruby

I'm looking for a way to call a single Capistrano task to perform different things to different roles. Is Capistrano able to do this, or do I have write a specific task for each role?

The standard way to do this in Capistrano:
task :whatever, :roles => [:x, :y, :z] do
x_tasks
y_tasks
z_tasks
end
task :x_tasks, :roles => :x do
#...
end
task :y_tasks, :roles => :y do
#...
end
task :z_tasks, :roles => :z do
#...
end
So yes, you do need to write separate tasks, but you can call them from a parent task and they will filter appropriately.

Actually no:
% cat capfile
server 'localhost', :role2
task :task1, :roles=>:role1 do
puts 'task1'
end
task :task2 do
task1
end
% cap task2
* executing `task2'
* executing `task1'
task1
The :roles param is passed further to run command etc but does not seem to affect whether the task is actually fired.
Sorry, didn't find the way to put a comment on comment so I've written it here.

You can also do
task :foo do
run "command", :roles => :some_role
upload "source", "destination", :roles => :another_role
end

Use namespacing:
https://github.com/leehambley/capistrano-handbook/blob/master/index.markdown#namespacing-tasks
namespace :backup do
task :default do
web
db
end
task :web, :roles => :web do
puts "Backing Up Web Server"
end
task :db, :roles => :db do
puts "Backing Up DB Server"
end
end
these tasks show up in a cap -T as
backup:default
backup:web
backup:db

There is a way, kind of. Check: http://weblog.rubyonrails.org/2006/8/30/capistrano-1-1-9-beta/ and you'll see that you can override the default roles using the ROLES environment variable.
I have a task defined as:
desc "A simple test to show we can ssh into all servers"
task :echo_hello, :roles => :test do
run "echo 'hello, world!'"
end
The :test role is assigned to one server.
On the command line, I can run:
[james#fluffyninja bin]$ cap echo_hello ROLES=lots_of_servers
And the task will now run on the lots_of_servers role.
I have not verified that this works inside a ruby script by updating the ENV hash, but this is a good start.

Only for the record, this could be a solution using Capistrano 3:
desc "Do something specific for 3 different servers with 3 different roles"
task :do_something do
on roles(:api_role), in: :sequence do
# do something in api server
end
on roles(:app_role), in: :sequence do
# do something in application server
end
on roles(:another_role), in: :sequence do
# do something in another server
end
end
The sever definition to perform "do_something" task in a application server would be something like:
server 'application.your.domain', user: 'deploy', roles: %w{app_role}
Then you can call the task (there are several ways to do it) and the task will execute specific instructions according to the "app_role".

Related

Why does my Rake task not work properly on the first run?

Background
I have a rake task called prepare to update my hosts.txt file based on an environment variable I set i.e: rake spec environment=test
The mule task reads from the hosts variable in order to run the rspec tests.
Rakefile
require 'rake'
require 'rspec/core/rake_task'
hosts = IO.readlines('./hosts.txt').sort!
task :spec => 'spec:prepare'
task :spec => 'spec:mule_esb'
namespace :spec do
task :prepare do
sh ("cd ../capistrano && cap OVS_#{ENV['environment']} admin:trigger_serverspec_hosts")
end
task :mule_esb => hosts
hosts.each do |host|
begin
desc "Run serverspec on #{host}"
RSpec::Core::RakeTask.new(host) do |t|
ENV['TARGET_HOST'] = host
puts "\u2630 #{host.upcase}"
# Write to file and stdout in documentation format
t.rspec_opts = '--out rspec_results.txt --format documentation'
t.pattern = "spec/mule_esb/*_spec.rb"
t.verbose = false
# Stop serverspec from early termination if it fails on a single host
# Exit code will always be zero
t.fail_on_error = false
end
rescue
end
end
end
As you can see I have ordered the tasks as follows:
task :spec => 'spec:prepare'
task :spec => 'spec:mule_esb'
Observation
Command: rake spec environment=test
prepare task runs successfully and updates the hosts.txt file
but the mule_esb task runs from hosts set as part of the
previous run (when environment was staging)
If I run it again without any changes it successfully runs it on the test environment hosts
Running the tasks individually works as expected
rake spec spec:prepare and then rake spec:mule_esb
I'm stumped as to why this is happening. I'm not all that familiar with Rake - Can someone explain this behaviour?
Do this
namespace :spec do
task prepare: :environment do
sh ("cd ../capistrano && cap OVS_#{ENV['environment']} proteus:trigger_serverspec_hosts")
end
end
specify environment with task name.

Rake preconfiguration?

How do I do this in the Ruby-way?
# Rakefile
desc "Run task on server #1"
task :one do
# Do somethin on server 1
Srv1.exec "..."
end
desc "Run task on server #2"
task :two do
# Do somethin on server 2
Srv2.exec "..."
end
desc "Run task on both servers"
task :both do
# Do somethin on both servers
Serv1.exec "..."
Serv2.exec "..."
end
How do I require the end execution configuration code? How do I do scaling (if I need Serv3)? Should Serv1 be a class or something else?
What I need to do is to control a Vagrant VM with Rake, run custom tasks inside Vagrant, synchronize data with the host system, run some code on production and send the result to Vagrant, etc.
At first, I'll fix a minor correction for your model. Do the both task with more simple method:
desc "Run task on both servers"
task :both => [ :one, :two ]
How do I require the end execution configuration code? How do I do scaling (if I need Serv3)? Should Serv1 be a class or something else?
Just create a class Serv, and pass to it a number of server or something else. So you'll get:
desc "Run task on a server"
task :one do
# Do somethin on a server
Serv.new( ENV[ 'SERVER_NUMBER' ] ).exec "..."
end
desc "Run task on all servers"
task :one do
# Do somethin on all servers
Serv.exec "..."
end
If you are trying to do some things on multiple servers, rake might not be quite the best tool for the job. Maybe you want to check out Capistrano which is usually used for deploying software, but also can be a tool to do general things remotely.
Ok. I've solved the problem myself with loading two files in head of Rakefile the first is server.rb with server class, the second is init.rb with server instantiating code Server1 = Server.new({:user=>'... which is accessable from tasks in Rackfile.

Namespaces in Ruby Rake Tasks

Are the following equivalent?
namespace :resque do
task setup: :environment do
end
end
task "resque:setup" => :environment do
end
In short: yes. When running rake resque:setup both of these tasks will be invoked.
Rake will merge these tasks. You can test this by doing the following:
p Rake.application.tasks
Which in this case would return something like
[<Rake::Task resque:setup => [environment]>]
Which is simply an Array holding a single Rake::Task object. You can also check the scope or list of namespaces for a task by doing:
p Rake.application.tasks.first.scope
#=> ["resque"]
If you want to learn a little more on how the internals of Rake work, check out Rake::Task and Rake::TaskManager

Run rake tasks in sequence

I have rake tasks which i want to run in proper sequence.
I want to run one rake task which run other rake tasks in proper sequence.
How may i do that?
you should consider defining dependencies between your tasks like this
task :primary => [:secondary]
task :secondary do
puts "Doing Secondary Task"
end
But if you really, really need to call the tasks directly you can use invoke to call another task
task :primary do
Rake::Task[:secondary].invoke
end
task :secondary do
puts "Doing Secondary Task"
end
see also here

How to run Rake tasks from within Rake tasks?

I have a Rakefile that compiles the project in two ways, according to the global variable $build_type, which can be :debug or :release (the results go in separate directories):
task :build => [:some_other_tasks] do
end
I wish to create a task that compiles the project with both configurations in turn, something like this:
task :build_all do
[ :debug, :release ].each do |t|
$build_type = t
# call task :build with all the tasks it depends on (?)
end
end
Is there a way to call a task as if it were a method? Or how can I achieve anything similar?
If you need the task to behave as a method, how about using an actual method?
task :build => [:some_other_tasks] do
build
end
task :build_all do
[:debug, :release].each { |t| build t }
end
def build(type = :debug)
# ...
end
If you'd rather stick to rake's idioms, here are your possibilities, compiled from past answers:
This always executes the task, but it doesn't execute its dependencies:
Rake::Task["build"].execute
This one executes the dependencies, but it only executes the task if
it has not already been invoked:
Rake::Task["build"].invoke
This first resets the task's already_invoked state, allowing the task to
then be executed again, dependencies and all:
Rake::Task["build"].reenable
Rake::Task["build"].invoke
Note that dependencies already invoked are not automatically re-executed unless they are re-enabled. In Rake >= 10.3.2, you can use the following to re-enable those as well:
Rake::Task["build"].all_prerequisite_tasks.each(&:reenable)
for example:
Rake::Task["db:migrate"].invoke
task :build_all do
[ :debug, :release ].each do |t|
$build_type = t
Rake::Task["build"].reenable
Rake::Task["build"].invoke
end
end
That should sort you out, just needed the same thing myself.
task :invoke_another_task do
# some code
Rake::Task["another:task"].invoke
end
task :build_all do
[ :debug, :release ].each do |t|
$build_type = t
Rake::Task["build"].execute
end
end
If you want each task to run regardless of any failures, you can do something like:
task :build_all do
[:debug, :release].each do |t|
ts = 0
begin
Rake::Task["build"].invoke(t)
rescue
ts = 1
next
ensure
Rake::Task["build"].reenable # If you need to reenable
end
return ts # Return exit code 1 if any failed, 0 if all success
end
end
I would suggest not to create general debug and release tasks if the project is really something that gets compiled and so results in files. You should go with file-tasks which is quite doable in your example, as you state, that your output goes into different directories.
Say your project just compiles a test.c file to out/debug/test.out and out/release/test.out with gcc you could setup your project like this:
WAYS = ['debug', 'release']
FLAGS = {}
FLAGS['debug'] = '-g'
FLAGS['release'] = '-O'
def out_dir(way)
File.join('out', way)
end
def out_file(way)
File.join(out_dir(way), 'test.out')
end
WAYS.each do |way|
desc "create output directory for #{way}"
directory out_dir(way)
desc "build in the #{way}-way"
file out_file(way) => [out_dir(way), 'test.c'] do |t|
sh "gcc #{FLAGS[way]} -c test.c -o #{t.name}"
end
end
desc 'build all ways'
task :all => WAYS.map{|way|out_file(way)}
task :default => [:all]
This setup can be used like:
rake all # (builds debug and release)
rake debug # (builds only debug)
rake release # (builds only release)
This does a little more as asked for, but shows my points:
output directories are created, as necessary.
the files are only recompiled if needed (this example is only correct for the simplest of test.c files).
you have all tasks readily at hand if you want to trigger the release build or the debug build.
this example includes a way to also define small differences between debug and release-builds.
no need to reenable a build-task that is parametrized with a global variable, because now the different builds have different tasks. the codereuse of the build-task is done by reusing the code to define the build-tasks. see how the loop does not execute the same task twice, but instead created tasks, that can later be triggered (either by the all-task or be choosing one of them on the rake commandline).

Resources