Question

I have the same problem described here:

How to call a Capistrano's task within another Capistrano's task?

However the workaround solution of rolling back to Capistrano v3.0.1 and sshkit 1.0 does not work for me.

Using this tutorial, I have declared custom tasks in lib/capistrano/tasks which use functions decalared in .rb files stored at lib/capistrano/

capfile:

# Load DSL and Setup Up Stages
require 'capistrano/setup'

# Includes default deployment tasks
require 'capistrano/deploy'

require 'capistrano/rails'

require 'capistrano/rvm'

# Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }

# Loads custom tasks from all folders below `lib/capistrano' if you have any defined.
Dir.glob('lib/capistrano/**/*.rb').each { |r| import r }

# because the above didn't quite look right to me, and all the .rbs are in /lib/capistrano
Dir.glob('lib/capistrano/*.rb').each { |r| import r }

deploy.rb

set :application, 'hello-rails'
set :app_shortname, 'hr'
set :repo_url, 'git@bitbutcket.org:me/myapp.git'  #<-substituted fake info here for this post

# Default value for :linked_files is []
set :linked_files, %w{config/database.yml config/application.yml}

# Default value for linked_dirs is []
 set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

# everything below this comment adapted from the talkingquickly tutorial
#which config files should be copied by deploy:setup_config
set(:config_files, %w(
  nginx.conf
  unicorn.rb
  unicorn_init.sh
))

# which config files should be made executable after copying
# by deploy:setup_config
set(:executable_config_files, %w(unicorn_init.sh))

# files which need to be symlinked
set(:symlinks, [
  {
    source: "nginx.conf",
    link: "/etc/nginx/sites-enabled/{{full_app_name}}"
  },
  {
    source: "unicorn_init.sh",
    link: "/etc/init.d/unicorn_{{full_app_name}}"
  }
])

#specify my ruby version
set :rvm_type, :system

#specify my gemset
set :rvm_ruby_version, '1.9.3-p385@hello-rails'

namespace :deploy do
  # make sure we're deploying what we think we're deploying
  before :deploy, "deploy:check_revision"
  # only allow a deploy with passing tests to deployed
  before :deploy, "deploy:run_tests"
  # compile assets locally then rsync
  after 'deploy:symlink:shared', 'deploy:compile_assets_locally'
  after :finishing, 'deploy:cleanup'
end

config/staging.rb

set :stage, :staging
set :branch, "cap-rails"
set :rails_env, :test

# used in case we're deploying multiple versions of the same
# app side by side. Also provides quick sanity checks when looking
# at filepaths
set :full_app_name, "#{fetch(:app_shortname)}_#{fetch(:stage)}"

set :deploy_to, "/srv/#{fetch(:app_shortname)}"

# number of unicorn workers
set :unicorn_worker_count, 5

# For building nginx config file
set :enable_ssl, false

# extended properties on the server.
server 'dev', user: 'deployer', roles: %w{web app}  #<-substituted fake info here for this post

# custom ssh options
 set :ssh_options, {
   user: 'deployer',  #<-substituted fake info here for this post
   keys: %w(path.to.key),  #<-substituted fake info here for this post
   forward_agent: true,
   auth_methods: %w(publickey)
 }

lib/capistrano/tasks/setup_config.cap

namespace :deploy do
  task :setup_config do
    on roles(:app) do
      # make the config dir
      execute :mkdir, "-p #{shared_path}/config"
      full_app_name = fetch(:full_app_name)

      # config files to be uploaded to shared/config, see the
      # definition of smart_template for details of operation.
      # Essentially looks for #{filename}.erb in deploy/#{full_app_name}/
      # and if it isn't there, falls back to deploy/#{shared}. Generally
      # everything should be in deploy/shared with params which differ
      # set in the stage files
      config_files = fetch(:config_files)
      config_files.each do |file|
        smart_template file
      end

      # which of the above files should be marked as executable
      executable_files = fetch(:executable_config_files)
      executable_files.each do |file|
        execute :chmod, "+x #{shared_path}/config/#{file}"
      end

      # symlink stuff which should be... symlinked
      symlinks = fetch(:symlinks)

      symlinks.each do |symlink|
        sudo "ln -nfs #{shared_path}/config/#{symlink[:source]} #{sub_strings(symlink[:link])}"
      end
    end
  end
end

lib/capistrano/template.rb

def smart_template(from, to=nil)
  to ||= from
  full_to_path = "#{shared_path}/config/#{to}"
  if from_erb_path = template_file(from)
    from_erb = StringIO.new(ERB.new(File.read(from_erb_path)).result(binding))
    upload! from_erb, full_to_path
    info "copying: #{from_erb} to: #{full_to_path}"
  else
    error "error #{from} not found"
  end
end

def template_file(name)
  if File.exist?((file = "config/deploy/#{fetch(:full_app_name)}/#{name}.erb"))
    return file
  elsif File.exist?((file = "config/deploy/shared/#{name}.erb"))
    return file
  end
  return nil
end

Gemfile

    source 'https://rubygems.org'

    # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
    gem 'rails', '4.0.4'

    # Use sqlite3 as the database for Active Record
    gem 'sqlite3'

    # Use SCSS for stylesheets
    gem 'sass-rails', '~> 4.0.2'

    # Use Uglifier as compressor for JavaScript assets
    gem 'uglifier', '>= 1.3.0'

    # Use CoffeeScript for .js.coffee assets and views
    gem 'coffee-rails', '~> 4.0.0'

    # Use jquery as the JavaScript library
    gem 'jquery-rails'

    # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
    gem 'turbolinks'

    # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
    gem 'jbuilder', '~> 1.2'

    #because sshkit 1.3 has this bug: https://stackoverflow.com/questions/21401665/how-to-call-a-capistranos-task-within-another-capistranos-task
    gem 'sshkit', '~> 1.0.0'
    gem 'capistrano',  '~> 3.0.1'

    gem 'capistrano-rails', '~> 1.1'
    gem 'capistrano-rvm'

    group :doc do
      # bundle exec rake doc:rails generates the API under doc/api.
      gem 'sdoc', require: false
    end

Gemfile.lock

GEM
  remote: https://rubygems.org/
  specs:
    actionmailer (4.0.4)
      actionpack (= 4.0.4)
      mail (~> 2.5.4)
    actionpack (4.0.4)
      activesupport (= 4.0.4)
      builder (~> 3.1.0)
      erubis (~> 2.7.0)
      rack (~> 1.5.2)
      rack-test (~> 0.6.2)
    activemodel (4.0.4)
      activesupport (= 4.0.4)
      builder (~> 3.1.0)
    activerecord (4.0.4)
      activemodel (= 4.0.4)
      activerecord-deprecated_finders (~> 1.0.2)
      activesupport (= 4.0.4)
      arel (~> 4.0.0)
    activerecord-deprecated_finders (1.0.3)
    activesupport (4.0.4)
      i18n (~> 0.6, >= 0.6.9)
      minitest (~> 4.2)
      multi_json (~> 1.3)
      thread_safe (~> 0.1)
      tzinfo (~> 0.3.37)
    arel (4.0.2)
    atomic (1.1.16)
    builder (3.1.4)
    capistrano (3.0.1)
      i18n
      rake (>= 10.0.0)
      sshkit (>= 0.0.23)
    capistrano-bundler (1.0.0)
      capistrano (>= 3.0.0.pre)
    capistrano-rails (1.1.0)
      capistrano (>= 3.0.0)
      capistrano-bundler (>= 1.0.0)
    capistrano-rvm (0.0.3)
      capistrano
    coffee-rails (4.0.1)
      coffee-script (>= 2.2.0)
      railties (>= 4.0.0, < 5.0)
    coffee-script (2.2.0)
      coffee-script-source
      execjs
    coffee-script-source (1.7.0)
    erubis (2.7.0)
    execjs (2.0.2)
    hike (1.2.3)
    i18n (0.6.9)
    jbuilder (1.5.3)
      activesupport (>= 3.0.0)
      multi_json (>= 1.2.0)
    jquery-rails (3.1.0)
      railties (>= 3.0, < 5.0)
      thor (>= 0.14, < 2.0)
    json (1.8.1)
    mail (2.5.4)
      mime-types (~> 1.16)
      treetop (~> 1.4.8)
    mime-types (1.25.1)
    minitest (4.7.5)
    multi_json (1.9.2)
    net-scp (1.1.2)
      net-ssh (>= 2.6.5)
    net-ssh (2.8.0)
    polyglot (0.3.4)
    rack (1.5.2)
    rack-test (0.6.2)
      rack (>= 1.0)
    rails (4.0.4)
      actionmailer (= 4.0.4)
      actionpack (= 4.0.4)
      activerecord (= 4.0.4)
      activesupport (= 4.0.4)
      bundler (>= 1.3.0, < 2.0)
      railties (= 4.0.4)
      sprockets-rails (~> 2.0.0)
    railties (4.0.4)
      actionpack (= 4.0.4)
      activesupport (= 4.0.4)
      rake (>= 0.8.7)
      thor (>= 0.18.1, < 2.0)
    rake (10.2.2)
    rdoc (4.1.1)
      json (~> 1.4)
    sass (3.2.18)
    sass-rails (4.0.2)
      railties (>= 4.0.0, < 5.0)
      sass (~> 3.2.0)
      sprockets (~> 2.8, <= 2.11.0)
      sprockets-rails (~> 2.0.0)
    sdoc (0.4.0)
      json (~> 1.8)
      rdoc (~> 4.0, < 5.0)
    sprockets (2.11.0)
      hike (~> 1.2)
      multi_json (~> 1.0)
      rack (~> 1.0)
      tilt (~> 1.1, != 1.3.0)
    sprockets-rails (2.0.1)
      actionpack (>= 3.0)
      activesupport (>= 3.0)
      sprockets (~> 2.8)
    sqlite3 (1.3.9)
    sshkit (1.0.0)
      net-scp
      net-ssh
      term-ansicolor
    term-ansicolor (1.3.0)
      tins (~> 1.0)
    thor (0.19.1)
    thread_safe (0.3.1)
      atomic (>= 1.1.7, < 2)
    tilt (1.4.1)
    tins (1.0.1)
    treetop (1.4.15)
      polyglot
      polyglot (>= 0.3.1)
    turbolinks (2.2.1)
      coffee-rails
    tzinfo (0.3.39)
    uglifier (2.5.0)
      execjs (>= 0.3.0)
      json (>= 1.8.0)

PLATFORMS
  ruby

DEPENDENCIES
  capistrano (~> 3.0.1)
  capistrano-rails (~> 1.1)
  capistrano-rvm
  coffee-rails (~> 4.0.0)
  jbuilder (~> 1.2)
  jquery-rails
  rails (= 4.0.4)
  sass-rails (~> 4.0.2)
  sdoc
  sqlite3
  sshkit (~> 1.0.0)
  turbolinks
  uglifier (>= 1.3.0)

When I execute bundle exec cap staging deploy:setup_config

I get this output:

INFO [9e71d728] Running mkdir -p /srv/hr/shared/config on ruby-dev
DEBUG [9e71d728] Command: mkdir -p /srv/hr/shared/config
 INFO [9e71d728] Finished in 0.738 seconds command successful.
cap aborted!
NoMethodError: undefined method `smart_template' for #<SSHKit::Backend::Netssh:0x007f8a4dc04980>
/Users/nico/DevOps/repo/hello-rails/lib/capistrano/tasks/setup_config.cap:16:in `block (4 levels) in <top (required)>'
/Users/nico/DevOps/repo/hello-rails/lib/capistrano/tasks/setup_config.cap:15:in `each'
/Users/nico/DevOps/repo/hello-rails/lib/capistrano/tasks/setup_config.cap:15:in `block (3 levels) in <top (required)>'
/Users/nico/.rvm/gems/ruby-1.9.3-p385@hello-rails-cap-v3-sshkit-v1/gems/sshkit-1.0.0/lib/sshkit/backends/netssh.rb:42:in `instance_exec'
/Users/nico/.rvm/gems/ruby-1.9.3-p385@hello-rails-cap-v3-sshkit-v1/gems/sshkit-1.0.0/lib/sshkit/backends/netssh.rb:42:in `run'

It appears that using capistrano v3.0.1 and sshkit v1.0 are not resolving my issue. The deploy:setup_config task is attempting to call the function smart_template as defined in lib/capistrano/template.rb, and the output indicates that the function cannot be found. I'm at a loss for how to get this working properly. Any advise on addressing the issue is welcome. Also, if a better approach to creating the config and executable files for nginx and unicorn exists I'd love to hear about that.

EDIT

After suspecting that the sshkit gem from rubygems still containing the bug, I added

gem 'sshkit', :git => 'https://github.com/capistrano/sshkit.git' 

to my Capfile and rebuilt my local gemset. This didn't address the issue, however, and directed me to look elsewhere. From there I was able to diagnose the issue as being related to the import of the ruby files defined by these lines in my Capfile:

Dir.glob('lib/capistrano/*.rb').each { |r| import r }
Dir.glob('lib/capistrano/**/*.rb').each { |r| import r }

I commented the lines out and replaced them with:

require_relative 'lib/capistrano/template.rb'
require_relative 'lib/capistrano/substitute_strings.rb'

and the functions are now called properly by my deploy:setup_config task. I exported the working gem set and created a new gemset using sshkit from ruby gems. With the require_relative lines listed above, the sshkit gem from rubygems worked fine. So the issue was never with the bug in sshkit, regardless of which source I was using (git or rubygems), but with the imports of the ruby files containing the functions that my cap task was calling.

Was it helpful?

Solution

There are two problems with your example, the correct lines are:

Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }                                                                                                                                      
Dir.glob('lib/capistrano/**/*.rb').each { |r| import r }    

You can see the complete example here

OTHER TIPS

Per my edit in the original question, the source of the sshkit gem was not my issue. Rather, the issue was with the import statements.

Changing the lines:

# Loads custom tasks from all folders below `lib/capistrano' if you have any defined.
Dir.glob('lib/capistrano/*.rb').each { |r| import r }
Dir.glob('lib/capistrano/**/*.rb').each { |r| import r }

to:

require_relative 'lib/capistrano/template.rb'
require_relative 'lib/capistrano/substitute_strings.rb'

addressed the issue for me.

I'm not sure why the Dir.glob method didn't work for me, I assume operator error with the expectation that figuring out what I did wrong will be useful for generalizing imports in future cap deployments.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top