capistrano / composer

Capistrano extension for Composer tasks
MIT License
183 stars 48 forks source link

Command mapping #56

Open pmeinhardt opened 5 years ago

pmeinhardt commented 5 years ago

Hey there, thanks for creating this gem 🙇

I've run into an issue when following the instructions in the README as the shared_path is not necessarily set correctly when you define the command mapping in the top-level scope.

Instead, what worked for me was to defer the mapping until after deploy:starting, where we can be sure the shared_path is initialized to the correct value:

namespace :deploy do
  # Add command mappings
  after :starting, :map_commands do
    SSHKit.config.command_map[:composer] = "php #{shared_path.join('composer.phar')}"
  end

  # Install composer (https://getcomposer.org/)
  after :starting, 'composer:install_executable'

  # …
end

Maybe it'd make sense to update the README and composer:install_executable task description?

pmeinhardt commented 5 years ago

Mmh 🤔 Setting up the mapping after deploy:starting wasn't such a great idea. This means that the command will only work properly when you run a deploy:* task that also invokes deploy:starting (I should've seen that).

However, as mentioned before, setting up the mapping in the top scope in config/deploy.rb breaks for me, as I am using a different :deploy_to path per stage, e.g.

# config/deploy/staging.rb
set :deploy_to, '/var/www/app-staging'
# config/deploy/production.rb
set :deploy_to, '/var/www/app-production'

When I set up the command mapping in config/deploy.rb, I get the following result:

set :application, 'app'
SSHKit.config.command_map[:composer] = "php #{shared_path.join('composer.phar')}"

Printing the mapping in a task (say maptest) and running cap staging maptest yields:

php /var/www/app/shared/composer.phar

It should be:

php /var/www/app-staging/shared/composer.phar

When I define the command mapping in the separate stage files (after the final, stage-specific :deploy_to value is set), everything works as expected. This totally makes sense 😁 That does mean however, that I need to duplicate the command mappings for every stage 😞

I'll probably find a cleaner solution though and post it here when I find it 🙂

Then maybe updating the instructions here could be worth it for people who run into a similar issue.

Cheers

pmeinhardt commented 5 years ago

Okay, so the following works for all my use-cases (running composer tasks during deploy but also separately):

# config/deploy.rb

stages.each do |stage|
  after stage, :map_commands do
    SSHKit.config.command_map[:composer] = "php #{shared_path.join('composer.phar')}"
  end
end

This defers the command mapping until all the stage variables, in particular :deploy_to, have been initialized with their final values.

Should this be mentioned in the description for the composer:install_executable task or what would be the best place for it?

I am also wondering whether this would be relevant for the general Capistrano FAQs or some place similar @capistrano?

In hindsight it makes a lot of sense, but initially I didn't think too much about the order in which the general deploy config, stage config and command mappings are initialized. I guess others might run into this too.

Thank you all for these super-helpful tools 🙇

pmeinhardt commented 5 years ago

An (more involved) alternative could also be for Capistrano to provide a complete wrapper around the SSHKit command map, which might look like the following:

# Define a command mapping with lazy initialization
command :composer, -> { "php #{shared_path.join('composer.phar'}" }

# Read command mappings
commands[:composer]
# => 'php /var/www/…'

Not sure about how exactly this could be tied into SSHKit though or whether that would just remain on the Capistrano layer:

task :example do
  on roles(:all) do
    execute commands[:composer], '--version'
    # or (as before)
    execute :composer, '--version'
  end
end