mirror of
https://github.com/capistrano/capistrano
synced 2023-03-27 23:21:18 -04:00
Add a Plugin API
The Plugin class is a simple set of conventions for defining Capistrano plugins. This is to support the eventual migration of SCM logic out of Capistrano itself and into separate gems as plugins.
This commit is contained in:
parent
3b9c90327b
commit
dc59c248a7
3 changed files with 194 additions and 0 deletions
106
lib/capistrano/plugin.rb
Normal file
106
lib/capistrano/plugin.rb
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
require "capistrano/all"
|
||||||
|
require "capistrano/ext/sshkit/backend/thread_local"
|
||||||
|
require "rake/tasklib"
|
||||||
|
|
||||||
|
# Base class for Capistrano plugins. Makes building a Capistrano plugin as easy
|
||||||
|
# as writing a `Capistrano::Plugin` subclass and overriding any or all of its
|
||||||
|
# three template methods:
|
||||||
|
#
|
||||||
|
# * set_defaults
|
||||||
|
# * register_hooks
|
||||||
|
# * define_tasks
|
||||||
|
#
|
||||||
|
# Within the plugin you can use any methods of the Rake or Capistrano DSLs, like
|
||||||
|
# `fetch`, `invoke`, etc. In cases when you need to use SSHKit's backend outside
|
||||||
|
# of an `on` block, use the `backend` convenience method. E.g. `backend.test`,
|
||||||
|
# `backend.execute`, or `backend.capture`.
|
||||||
|
#
|
||||||
|
# Package up and distribute your plugin class as a gem and you're good to go!
|
||||||
|
#
|
||||||
|
# To use a plugin, all a user has to do is instantiate it in the Capfile, like
|
||||||
|
# this:
|
||||||
|
#
|
||||||
|
# # Capfile
|
||||||
|
# require "capistrano/superfancy"
|
||||||
|
# Capistrano::Superfancy.new
|
||||||
|
#
|
||||||
|
# Or, to install the plugin without its hooks:
|
||||||
|
#
|
||||||
|
# # Capfile
|
||||||
|
# require "capistrano/superfancy"
|
||||||
|
# Capistrano::Superfancy.new(hooks: false)
|
||||||
|
#
|
||||||
|
class Capistrano::Plugin < Rake::TaskLib
|
||||||
|
include Capistrano::DSL
|
||||||
|
|
||||||
|
# Constructing a plugin "installs" it into Capistrano by loading its tasks,
|
||||||
|
# hooks, and defaults at the appropriate time. The hooks in particular can be
|
||||||
|
# skipped, if you want full control over when and how the plugin's tasks are
|
||||||
|
# executed. Simply pass `hooks:false` to opt out.
|
||||||
|
#
|
||||||
|
def initialize(hooks:true)
|
||||||
|
define_tasks
|
||||||
|
register_hooks if hooks
|
||||||
|
task "load:defaults" do
|
||||||
|
set_defaults
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Implemented by subclasses to provide default values for settings needed by
|
||||||
|
# this plugin. Typically done using the `set_if_empty` Capistrano DSL method.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# def set_defaults
|
||||||
|
# set_if_empty :my_plugin_option, true
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def set_defaults; end
|
||||||
|
|
||||||
|
# Implemented by subclasses to hook into Capistrano's deployment flow using
|
||||||
|
# using the `before` and `after` DSL methods. Note that `register_hooks` will
|
||||||
|
# not be called if the user has opted-out of hooks when installing the plugin.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# def register_hooks
|
||||||
|
# after "deploy:updated", "my_plugin:do_something"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def register_hooks; end
|
||||||
|
|
||||||
|
# Implemented by subclasses to define Rake tasks. Typically a plugin will call
|
||||||
|
# `eval_rakefile` to load Rake tasks from a separate .rake file.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# def define_tasks
|
||||||
|
# eval_rakefile File.expand_path("../tasks.rake", __FILE__)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# For simple tasks, you can define them inline. No need for a separate file.
|
||||||
|
#
|
||||||
|
# def define_tasks
|
||||||
|
# desc "Do something fantastic."
|
||||||
|
# task "my_plugin:fantastic" do
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def define_tasks; end
|
||||||
|
|
||||||
|
# Read and eval a .rake file in such a way that `self` within the .rake file
|
||||||
|
# refers to this plugin instance. This gives the tasks in the file access to
|
||||||
|
# helper methods defined by the plugin.
|
||||||
|
def eval_rakefile(path)
|
||||||
|
contents = IO.read(path)
|
||||||
|
instance_eval(contents, path, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convenience to access the current SSHKit backend outside of an `on` block.
|
||||||
|
def backend
|
||||||
|
SSHKit::Backend.current
|
||||||
|
end
|
||||||
|
end
|
82
spec/lib/capistrano/plugin_spec.rb
Normal file
82
spec/lib/capistrano/plugin_spec.rb
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
require "spec_helper"
|
||||||
|
require "capistrano/plugin"
|
||||||
|
|
||||||
|
module Capistrano
|
||||||
|
describe Plugin do
|
||||||
|
include Rake::DSL
|
||||||
|
include Capistrano::DSL
|
||||||
|
|
||||||
|
class DummyPlugin < Capistrano::Plugin
|
||||||
|
def define_tasks
|
||||||
|
task :hello do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def register_hooks
|
||||||
|
before "deploy:published", "hello"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ExternalTasksPlugin < Capistrano::Plugin
|
||||||
|
def define_tasks
|
||||||
|
eval_rakefile(
|
||||||
|
File.expand_path("../../../support/tasks/plugin.rake", __FILE__)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Called from plugin.rake to demonstrate that helper methods work
|
||||||
|
def hello
|
||||||
|
set :plugin_result, "hello"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
# Define an example task to allow testing hooks
|
||||||
|
task "deploy:published"
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
# Clean up any tasks or variables we created during the tests
|
||||||
|
Rake::Task.clear
|
||||||
|
Capistrano::Configuration.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
it "defines tasks when constructed" do
|
||||||
|
DummyPlugin.new
|
||||||
|
expect(Rake::Task["hello"]).not_to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "registers hooks when constructed" do
|
||||||
|
DummyPlugin.new
|
||||||
|
expect(Rake::Task["deploy:published"].prerequisites).to include("hello")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "skips registering hooks if :hooks => false" do
|
||||||
|
DummyPlugin.new(:hooks => false)
|
||||||
|
expect(Rake::Task["deploy:published"].prerequisites).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't call set_defaults immediately" do
|
||||||
|
dummy = DummyPlugin.new
|
||||||
|
dummy.expects(:set_defaults).never
|
||||||
|
end
|
||||||
|
|
||||||
|
it "calls set_defaults during load:defaults" do
|
||||||
|
dummy = DummyPlugin.new
|
||||||
|
dummy.expects(:set_defaults).once
|
||||||
|
Rake::Task["load:defaults"].invoke
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is able to load tasks from a .rake file" do
|
||||||
|
ExternalTasksPlugin.new
|
||||||
|
Rake::Task["plugin_test"].invoke
|
||||||
|
expect(fetch(:plugin_result)).to eq("hello")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "exposes the SSHKit backend to subclasses" do
|
||||||
|
SSHKit::Backend.expects(:current).returns(:backend)
|
||||||
|
plugin = DummyPlugin.new
|
||||||
|
expect(plugin.send(:backend)).to eq(:backend)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
6
spec/support/tasks/plugin.rake
Normal file
6
spec/support/tasks/plugin.rake
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# This rake file is used by plugin_spec.rb.
|
||||||
|
|
||||||
|
task :plugin_test do
|
||||||
|
# Example of invoking a helper method provided by the plugin
|
||||||
|
hello
|
||||||
|
end
|
Loading…
Reference in a new issue