├── .gitignore ├── lib ├── slackistrano │ ├── version.rb │ ├── messaging │ │ ├── null.rb │ │ ├── default.rb │ │ ├── helpers.rb │ │ └── base.rb │ ├── tasks │ │ └── slack.rake │ └── capistrano.rb └── slackistrano.rb ├── .travis.yml ├── images ├── slackistrano.png └── custom_messaging.jpg ├── Gemfile ├── Rakefile ├── spec ├── capistrano_deploy_stubs.rake ├── messaging │ ├── null_spec.rb │ └── helpers_spec.rb ├── disabling_posting_to_slack_spec.rb ├── posting_to_multiple_channels_spec.rb ├── nil_payload_spec.rb ├── dry_run_spec.rb ├── spec_helper.rb └── task_hooks_spec.rb ├── LICENSE.txt ├── slackistrano.gemspec ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | Gemfile.lock 3 | tmp 4 | -------------------------------------------------------------------------------- /lib/slackistrano/version.rb: -------------------------------------------------------------------------------- 1 | module Slackistrano 2 | VERSION = '4.0.2' 3 | end 4 | -------------------------------------------------------------------------------- /lib/slackistrano.rb: -------------------------------------------------------------------------------- 1 | require 'slackistrano/version' 2 | 3 | module Slackistrano 4 | end 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | cache: bundler 4 | rvm: 5 | - 2.6 6 | - 2.5 7 | -------------------------------------------------------------------------------- /images/slackistrano.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phallstrom/slackistrano/HEAD/images/slackistrano.png -------------------------------------------------------------------------------- /images/custom_messaging.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phallstrom/slackistrano/HEAD/images/custom_messaging.jpg -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in slackistrano.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | task :default => :spec 4 | require 'rspec/core/rake_task' 5 | RSpec::Core::RakeTask.new 6 | -------------------------------------------------------------------------------- /spec/capistrano_deploy_stubs.rake: -------------------------------------------------------------------------------- 1 | namespace :deploy do 2 | task :starting do 3 | end 4 | task :updating do 5 | end 6 | task :reverting do 7 | end 8 | task :finishing do 9 | end 10 | task :finishing_rollback do 11 | end 12 | task :failed do 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/messaging/null_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Slackistrano::Messaging::Null do 4 | subject(:messaging) { Slackistrano::Messaging::Null.new } 5 | 6 | %w[updating reverting updated reverted failed].each do |stage| 7 | it "returns no payload for on #{stage}" do 8 | expect(messaging.payload_for(stage)).to be_nil 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/disabling_posting_to_slack_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Slackistrano do 4 | context "when :slackistrano is :disabled" do 5 | before(:all) do 6 | set :slackistrano, false 7 | end 8 | 9 | %w[starting updating reverting updated reverted failed].each do |stage| 10 | it "doesn't post on slack:deploy:#{stage}" do 11 | expect_any_instance_of(Slackistrano::Capistrano).not_to receive(:post) 12 | Rake::Task["slack:deploy:#{stage}"].execute 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/slackistrano/messaging/null.rb: -------------------------------------------------------------------------------- 1 | module Slackistrano 2 | module Messaging 3 | class Null < Base 4 | def payload_for_starting 5 | nil 6 | end 7 | 8 | def payload_for_updating 9 | nil 10 | end 11 | 12 | def payload_for_reverting 13 | nil 14 | end 15 | 16 | def payload_for_updated 17 | nil 18 | end 19 | 20 | def payload_for_reverted 21 | nil 22 | end 23 | 24 | def payload_for_failed 25 | nil 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/posting_to_multiple_channels_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Slackistrano do 4 | before(:all) do 5 | set :slackistrano, { 6 | channel: %w[one two] 7 | } 8 | end 9 | 10 | context "when :slack_channel is an array" do 11 | %w[starting updating reverting updated reverted failed].each do |stage| 12 | it "posts to slack on slack:deploy:#{stage} in every channel" do 13 | expect_any_instance_of(Slackistrano::Capistrano).to receive(:post).twice 14 | Rake::Task["slack:deploy:#{stage}"].execute 15 | end 16 | end 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /lib/slackistrano/messaging/default.rb: -------------------------------------------------------------------------------- 1 | module Slackistrano 2 | module Messaging 3 | class Default < Base 4 | 5 | def payload_for_starting 6 | super 7 | end 8 | 9 | def payload_for_updating 10 | super 11 | end 12 | 13 | def payload_for_reverting 14 | super 15 | end 16 | 17 | def payload_for_updated 18 | super 19 | end 20 | 21 | def payload_for_reverted 22 | super 23 | end 24 | 25 | def payload_for_failed 26 | super 27 | end 28 | 29 | def channels_for(action) 30 | super 31 | end 32 | 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/nil_payload_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class NilPayloadMessaging < Slackistrano::Messaging::Default 4 | def payload_for_updating 5 | nil 6 | end 7 | 8 | def channels_for(action) 9 | "testing" 10 | end 11 | end 12 | 13 | describe Slackistrano do 14 | before(:all) do 15 | set :slackistrano, { klass: NilPayloadMessaging } 16 | end 17 | 18 | it "does not post on updating" do 19 | expect_any_instance_of(Slackistrano::Capistrano).not_to receive(:post) 20 | Rake::Task["slack:deploy:updating"].execute 21 | end 22 | 23 | it "posts on updated" do 24 | expect_any_instance_of(Slackistrano::Capistrano).to receive(:post).and_return(true) 25 | Rake::Task["slack:deploy:updated"].execute 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/dry_run_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class DryRunMessaging < Slackistrano::Messaging::Default 4 | def channels_for(action) 5 | "testing" 6 | end 7 | end 8 | 9 | describe Slackistrano do 10 | before(:all) do 11 | set :slackistrano, { klass: DryRunMessaging } 12 | end 13 | 14 | %w[starting updating reverting updated reverted failed].each do |stage| 15 | it "does not post to slack on slack:deploy:#{stage}" do 16 | allow_any_instance_of(Slackistrano::Capistrano).to receive(:dry_run?).and_return(true) 17 | expect_any_instance_of(Slackistrano::Capistrano).to receive(:post_dry_run) 18 | expect_any_instance_of(Slackistrano::Capistrano).not_to receive(:post_to_slack) 19 | Rake::Task["slack:deploy:#{stage}"].execute 20 | end 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | require 'capistrano/all' 4 | require 'capistrano/setup' 5 | load 'capistrano_deploy_stubs.rake' 6 | require 'slackistrano' 7 | require 'slackistrano/capistrano' 8 | require 'rspec' 9 | require 'pry' 10 | 11 | # Requires supporting files with custom matchers and macros, etc, 12 | # in ./support/ and its subdirectories. 13 | Dir['#{File.dirname(__FILE__)}/support/**/*.rb'].each {|f| require f} 14 | 15 | RSpec.configure do |config| 16 | config.order = 'random' 17 | config.filter_run :focus 18 | config.run_all_when_everything_filtered = true 19 | config.fail_fast = 1 20 | end 21 | 22 | # Silence rake's '** Execute…' output 23 | Rake.application.options.trace = false 24 | -------------------------------------------------------------------------------- /lib/slackistrano/messaging/helpers.rb: -------------------------------------------------------------------------------- 1 | module Slackistrano 2 | module Messaging 3 | module Helpers 4 | 5 | def icon_url 6 | options.fetch(:icon_url, 'https://raw.githubusercontent.com/phallstrom/slackistrano/master/images/slackistrano.png') 7 | end 8 | 9 | def icon_emoji 10 | options.fetch(:icon_emoji, nil) 11 | end 12 | 13 | def username 14 | options.fetch(:username, 'Slackistrano') 15 | end 16 | 17 | def deployer 18 | default = ENV['USER'] || ENV['USERNAME'] 19 | fetch(:local_user, default) 20 | end 21 | 22 | def branch 23 | fetch(:branch) 24 | end 25 | 26 | def application 27 | fetch(:application) 28 | end 29 | 30 | def stage(default = 'an unknown stage') 31 | fetch(:stage, default) 32 | end 33 | 34 | # 35 | # Return the elapsed running time as a string. 36 | # 37 | # Examples: 21-18:26:30, 15:28:37, 01:14 38 | # 39 | def elapsed_time 40 | `ps -p #{$$} -o etime=`.strip 41 | end 42 | 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Philip Hallstrom 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /slackistrano.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'slackistrano/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "slackistrano" 8 | gem.version = Slackistrano::VERSION 9 | gem.authors = ["Philip Hallstrom"] 10 | gem.email = ["philip@pjkh.com"] 11 | gem.description = %q{Send notifications to Slack about Capistrano deployments.} 12 | gem.summary = %q{Send notifications to Slack about Capistrano deployments.} 13 | gem.homepage = "https://github.com/phallstrom/slackistrano" 14 | gem.license = 'MIT' 15 | 16 | gem.required_ruby_version = '>= 2.0.0' 17 | 18 | gem.files = `git ls-files`.split($/) 19 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 20 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 21 | gem.require_paths = ["lib"] 22 | 23 | gem.add_dependency 'capistrano', '>= 3.8.1' 24 | gem.add_development_dependency 'rake' 25 | gem.add_development_dependency 'rspec' 26 | gem.add_development_dependency 'pry' 27 | 28 | # gem.post_install_message = %Q{ 29 | # } 30 | end 31 | -------------------------------------------------------------------------------- /spec/task_hooks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Slackistrano do 4 | 5 | describe "before/after hooks" do 6 | 7 | it "invokes slack:deploy:starting before deploy:starting" do 8 | expect(Rake::Task['deploy:starting'].prerequisites).to include 'slack:deploy:starting' 9 | end 10 | 11 | it "invokes slack:deploy:updating before deploy:updating" do 12 | expect(Rake::Task['deploy:updating'].prerequisites).to include 'slack:deploy:updating' 13 | end 14 | 15 | it "invokes slack:deploy:reverting before deploy:reverting" do 16 | expect(Rake::Task['deploy:reverting'].prerequisites).to include 'slack:deploy:reverting' 17 | end 18 | 19 | it "invokes slack:deploy:updated after deploy:finishing" do 20 | expect(Rake::Task['slack:deploy:updated']).to receive(:invoke) 21 | Rake::Task['deploy:finishing'].execute 22 | end 23 | 24 | it "invokes slack:deploy:reverted after deploy:finishing_rollback" do 25 | expect(Rake::Task['slack:deploy:reverted']).to receive(:invoke) 26 | Rake::Task['deploy:finishing_rollback'].execute 27 | end 28 | 29 | it "invokes slack:deploy:failed after deploy:failed" do 30 | expect(Rake::Task['slack:deploy:failed']).to receive(:invoke) 31 | Rake::Task['deploy:failed'].execute 32 | end 33 | 34 | it "invokes all slack:deploy tasks before slack:deploy:test" do 35 | expect(Rake::Task['slack:deploy:test'].prerequisites).to match %w[starting updating updated reverting reverted failed] 36 | end 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /lib/slackistrano/tasks/slack.rake: -------------------------------------------------------------------------------- 1 | namespace :slack do 2 | namespace :deploy do 3 | 4 | desc 'Notify about starting deploy' 5 | task :starting do 6 | Slackistrano::Capistrano.new(self).run(:starting) 7 | end 8 | 9 | desc 'Notify about updating deploy' 10 | task :updating do 11 | Slackistrano::Capistrano.new(self).run(:updating) 12 | end 13 | 14 | desc 'Notify about reverting deploy' 15 | task :reverting do 16 | Slackistrano::Capistrano.new(self).run(:reverting) 17 | end 18 | 19 | desc 'Notify about updated deploy' 20 | task :updated do 21 | Slackistrano::Capistrano.new(self).run(:updated) 22 | end 23 | 24 | desc 'Notify about reverted deploy' 25 | task :reverted do 26 | Slackistrano::Capistrano.new(self).run(:reverted) 27 | end 28 | 29 | desc 'Notify about failed deploy' 30 | task :failed do 31 | Slackistrano::Capistrano.new(self).run(:failed) 32 | end 33 | 34 | desc 'Test Slack integration' 35 | task :test => %i[starting updating updated reverting reverted failed] do 36 | # all tasks run as dependencies 37 | end 38 | 39 | end 40 | end 41 | 42 | unless fetch(:use_custom_slackistrano_hooks, false) 43 | before 'deploy:starting', 'slack:deploy:starting' 44 | before 'deploy:updating', 'slack:deploy:updating' 45 | before 'deploy:reverting', 'slack:deploy:reverting' 46 | after 'deploy:finishing', 'slack:deploy:updated' 47 | after 'deploy:finishing_rollback', 'slack:deploy:reverted' 48 | after 'deploy:failed', 'slack:deploy:failed' 49 | end -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Slackistrano Change Log 2 | 3 | 4.0.2 4 | ----- 5 | 6 | - Make it possible to overwrite all slackistrano hooks [#98] 7 | 8 | 4.0.1 9 | ----- 10 | 11 | - Send message on deploy:starting in addition to deploy:updating [#93] 12 | 13 | 4.0.0 14 | ----- 15 | 16 | - **BREAKING:** The Messaging::Deprecated has been removed. You must use 17 | the new configuration options as specified in the README. 18 | 19 | 3.8.3 20 | ----- 21 | 22 | - Fix improper boolean check when using not using slackbot and channels are empty [#90] 23 | 24 | 3.8.2 25 | ----- 26 | 27 | - Allow overriding icon_url, icon_emoji, and usename without needing custom 28 | messaging class [#78, #68] 29 | 30 | 3.8.1 31 | ----- 32 | 33 | - Changes to support capistrano 3.8.1 (hence the massive version bump) [#70, #71] 34 | 35 | 3.1.1 36 | ----- 37 | 38 | - Allow easily disabling Slackistrano by setting :slackistrano to false [#67] 39 | 40 | 3.1.0 41 | ----- 42 | 43 | - An entirely new way of customizing the messages sent to Slack. See the README for details. 44 | 45 | 3.0.0 46 | ----- 47 | 48 | - Require version 3.5.0 of Capistrano and utilize it's new dry-run functionality. 49 | 50 | 2.0.1 51 | ----- 52 | 53 | - Internal code refactoring. No public facing changes. 54 | 55 | 2.0.0 56 | ----- 57 | 58 | - **BREAKING:** You must now `require 'slackistrano/capistrano'` in your Capfile. 59 | Previously it was just `require 'slackistrano'`. It is also no longer necessary 60 | to add `require: false` in your Gemfile, but it won't hurt to leave it. 61 | 62 | 1.1.0 63 | ----- 64 | 65 | - Honor Capistrano's `--dry-run` [#33] 66 | - Better error reporting if Slack API returns failure [#40] 67 | - Allow posting to multiple channels by setting `:slack_channel` to an array [#37] 68 | 69 | 70 | 1.0.0 71 | ----- 72 | 73 | - **BREAKING:** Renamed all `***_starting` settings to `***_updating` 74 | - **BREAKING:** Renamed all `***_finished` settings to `***_updated` 75 | - Added rollback options `***_reverting` and `***_reverted` [#19, #31] 76 | -------------------------------------------------------------------------------- /lib/slackistrano/messaging/base.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | require_relative 'helpers' 3 | 4 | module Slackistrano 5 | module Messaging 6 | class Base 7 | 8 | include Helpers 9 | 10 | extend Forwardable 11 | def_delegators :env, :fetch 12 | 13 | attr_reader :team, :token, :webhook, :options 14 | 15 | def initialize(options = {}) 16 | @options = options.dup 17 | 18 | @env = options.delete(:env) 19 | @team = options.delete(:team) 20 | @channel = options.delete(:channel) 21 | @token = options.delete(:token) 22 | @webhook = options.delete(:webhook) 23 | end 24 | 25 | def payload_for_starting 26 | { 27 | text: "#{deployer} has started deploying branch #{branch} of #{application} to #{stage}" 28 | } 29 | end 30 | 31 | def payload_for_updating 32 | { 33 | text: "#{deployer} is deploying branch #{branch} of #{application} to #{stage}" 34 | } 35 | end 36 | 37 | def payload_for_reverting 38 | { 39 | text: "#{deployer} has started rolling back branch #{branch} of #{application} to #{stage}" 40 | } 41 | end 42 | 43 | def payload_for_updated 44 | { 45 | text: "#{deployer} has finished deploying branch #{branch} of #{application} to #{stage}" 46 | } 47 | end 48 | 49 | def payload_for_reverted 50 | { 51 | text: "#{deployer} has finished rolling back branch of #{application} to #{stage}" 52 | } 53 | end 54 | 55 | def payload_for_failed 56 | { 57 | text: "#{deployer} has failed to #{deploying? ? 'deploy' : 'rollback'} branch #{branch} of #{application} to #{stage}" 58 | } 59 | end 60 | 61 | def channels_for(action) 62 | @channel 63 | end 64 | 65 | ################################################################################ 66 | 67 | def payload_for(action) 68 | method = "payload_for_#{action}" 69 | respond_to?(method) && send(method) 70 | end 71 | 72 | def via_slackbot? 73 | @webhook.nil? 74 | end 75 | 76 | end 77 | end 78 | end 79 | 80 | require_relative 'default' 81 | require_relative 'null' 82 | -------------------------------------------------------------------------------- /spec/messaging/helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Slackistrano::Messaging::Default do 4 | 5 | describe "#icon_url" do 6 | it "returns a default" do 7 | expect(subject.icon_url).to match(/phallstrom.*slackistrano.png/) 8 | end 9 | 10 | it "returns a custom option" do 11 | allow(subject).to receive(:options).and_return(icon_url: 'http://example.com/foo.png') 12 | expect(subject.icon_url).to eq 'http://example.com/foo.png' 13 | end 14 | end 15 | 16 | describe "#icon_emoji" do 17 | it "returns a default of nil" do 18 | expect(subject.icon_emoji).to eq nil 19 | end 20 | 21 | it "returns a custom option" do 22 | allow(subject).to receive(:options).and_return(icon_emoji: ':thumbsup:') 23 | expect(subject.icon_emoji).to eq ':thumbsup:' 24 | end 25 | end 26 | 27 | describe "#username" do 28 | it "returns a default" do 29 | expect(subject.username).to eq 'Slackistrano' 30 | end 31 | 32 | it "returns a custom option" do 33 | allow(subject).to receive(:options).and_return(username: 'Codan the Deployer') 34 | expect(subject.username).to eq 'Codan the Deployer' 35 | end 36 | end 37 | 38 | describe '#deployer' do 39 | it "delegates to fetch" do 40 | expect(subject).to receive(:fetch).with(:local_user, anything) 41 | subject.deployer 42 | end 43 | 44 | it "has a default" do 45 | ENV['USER'] = 'cappy' 46 | expect(subject.deployer).to eq 'cappy' 47 | end 48 | end 49 | 50 | describe '#branch' do 51 | it "delegates to fetch" do 52 | expect(subject).to receive(:fetch).with(:branch) 53 | subject.branch 54 | end 55 | end 56 | 57 | describe '#application' do 58 | it "delegates to fetch" do 59 | expect(subject).to receive(:fetch).with(:application) 60 | subject.application 61 | end 62 | end 63 | 64 | describe '#stage' do 65 | it "delegates to fetch" do 66 | expect(subject).to receive(:fetch).with(:stage, anything) 67 | subject.stage 68 | end 69 | 70 | it "has a default" do 71 | expect(subject.stage('default')).to eq 'default' 72 | end 73 | end 74 | 75 | describe '#elapsed_time' do 76 | it "returns a time like string" do 77 | expect(subject.elapsed_time).to match /\A((\d+-)?\d+:)?\d\d:\d\d\Z/ 78 | end 79 | end 80 | 81 | end 82 | -------------------------------------------------------------------------------- /lib/slackistrano/capistrano.rb: -------------------------------------------------------------------------------- 1 | require_relative 'messaging/base' 2 | require 'net/http' 3 | require 'json' 4 | require 'forwardable' 5 | 6 | load File.expand_path("../tasks/slack.rake", __FILE__) 7 | 8 | module Slackistrano 9 | class Capistrano 10 | 11 | attr_reader :backend 12 | private :backend 13 | 14 | extend Forwardable 15 | def_delegators :env, :fetch, :run_locally 16 | 17 | def initialize(env) 18 | @env = env 19 | config = fetch(:slackistrano, {}) 20 | @messaging = if config 21 | opts = config.dup.merge(env: @env) 22 | klass = opts.delete(:klass) || Messaging::Default 23 | klass.new(opts) 24 | else 25 | Messaging::Null.new 26 | end 27 | end 28 | 29 | def run(action) 30 | _self = self 31 | run_locally { _self.process(action, self) } 32 | end 33 | 34 | def process(action, backend) 35 | @backend = backend 36 | 37 | payload = @messaging.payload_for(action) 38 | return if payload.nil? 39 | 40 | payload = { 41 | username: @messaging.username, 42 | icon_url: @messaging.icon_url, 43 | icon_emoji: @messaging.icon_emoji, 44 | }.merge(payload) 45 | 46 | channels = Array(@messaging.channels_for(action)) 47 | if !@messaging.via_slackbot? && channels.empty? 48 | channels = [nil] # default webhook channel 49 | end 50 | 51 | channels.each do |channel| 52 | post(payload.merge(channel: channel)) 53 | end 54 | end 55 | 56 | private ################################################## 57 | 58 | def post(payload) 59 | 60 | if dry_run? 61 | post_dry_run(payload) 62 | return 63 | end 64 | 65 | begin 66 | response = post_to_slack(payload) 67 | rescue => e 68 | backend.warn("[slackistrano] Error notifying Slack!") 69 | backend.warn("[slackistrano] Error: #{e.inspect}") 70 | end 71 | 72 | if response && response.code !~ /^2/ 73 | warn("[slackistrano] Slack API Failure!") 74 | warn("[slackistrano] URI: #{response.uri}") 75 | warn("[slackistrano] Code: #{response.code}") 76 | warn("[slackistrano] Message: #{response.message}") 77 | warn("[slackistrano] Body: #{response.body}") if response.message != response.body && response.body !~ / payload.to_json} 99 | uri = URI(@messaging.webhook) 100 | Net::HTTP.post_form(uri, params) 101 | end 102 | 103 | def dry_run? 104 | ::Capistrano::Configuration.env.dry_run? 105 | end 106 | 107 | def post_dry_run(payload) 108 | backend.info("[slackistrano] Slackistrano Dry Run:") 109 | if @messaging.via_slackbot? 110 | backend.info("[slackistrano] Team: #{@messaging.team}") 111 | backend.info("[slackistrano] Token: #{@messaging.token}") 112 | else 113 | backend.info("[slackistrano] Webhook: #{@messaging.webhook}") 114 | end 115 | backend.info("[slackistrano] Payload: #{payload.to_json}") 116 | end 117 | 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slackistrano 2 | 3 | [![Gem Version](https://badge.fury.io/rb/slackistrano.png)](http://badge.fury.io/rb/slackistrano) 4 | [![Code Climate](https://codeclimate.com/github/phallstrom/slackistrano.png)](https://codeclimate.com/github/phallstrom/slackistrano) 5 | [![Build Status](https://travis-ci.org/phallstrom/slackistrano.png?branch=master)](https://travis-ci.org/phallstrom/slackistrano) 6 | 7 | Send notifications to [Slack](https://slack.com) about [Capistrano](http://www.capistranorb.com) deployments. 8 | 9 | ## Requirements 10 | 11 | - Capistrano >= 3.8.1 12 | - Ruby >= 2.0 13 | - A Slack account 14 | 15 | ## Installation 16 | 17 | 1. Add this line to your application's Gemfile: 18 | 19 | ```ruby 20 | gem 'slackistrano' 21 | ``` 22 | 23 | 2. Execute: 24 | 25 | ``` 26 | $ bundle 27 | ``` 28 | 29 | 3. Require the library in your application's Capfile: 30 | 31 | ```ruby 32 | require 'slackistrano/capistrano' 33 | ``` 34 | 35 | ## Configuration 36 | 37 | You have two options to notify a channel in Slack when you deploy: 38 | 39 | 1. Using *Incoming WebHooks* integration, offering more options but requires 40 | one of the five free integrations. This option provides more messaging 41 | flexibility. 42 | 2. Using *Slackbot*, which will not use one of the five free integrations. 43 | 44 | ### Incoming Webhook 45 | 46 | 1. Configure your Slack's Incoming Webhook. 47 | 2. Add the following to `config/deploy.rb`: 48 | 49 | ```ruby 50 | set :slackistrano, { 51 | channel: '#your-channel', 52 | webhook: 'your-incoming-webhook-url' 53 | } 54 | ``` 55 | 56 | ### Slackbot 57 | 58 | 1. Configure your Slack's Slackbot (not Bot). 59 | 2. Add the following to `config/deploy.rb`: 60 | 61 | ```ruby 62 | set :slackistrano, { 63 | channel: '#your-channel', 64 | team: 'your-team-name', 65 | token: 'your-token' 66 | } 67 | ``` 68 | 69 | ### Optional Configuration & Overrides 70 | 71 | By default Slackistrano will use a default icon and username. These, can be 72 | overriden if you are using the default messaging class (ie. have not specified 73 | your own). 74 | 75 | 1. Configure per instructions above. 76 | 2. Add the following to `config/deploy.rb`: 77 | 78 | ```ruby 79 | set :slackistrano, { 80 | ... 81 | username: 'Foobar the Deployer', 82 | icon_emoji: ':thumbsup:', # takes precedence over icon_url 83 | icon_url: 'https://avatars2.githubusercontent.com/u/16705?v=4&s=40', 84 | ... 85 | } 86 | ``` 87 | 88 | 89 | ### Test your Configuration 90 | 91 | Test your setup by running the following command. This will post each stage's 92 | message to Slack in turn. 93 | 94 | ``` 95 | $ cap production slack:deploy:test 96 | ``` 97 | 98 | ## Usage 99 | 100 | Deploy your application like normal and you should see messages in the channel 101 | you specified. 102 | 103 | ## Customizing the hooks 104 | 105 | If you wish to take control over when and what slackistrano hooks are fired, then you can use the option in `deploy.rb`: 106 | 107 | ```ruby 108 | set :use_custom_slackistrano_hooks, true 109 | ``` 110 | 111 | This allows you to set custom hooks for all the slackistrano tasks: 112 | 113 | ```ruby 114 | 'slack:deploy:starting' 115 | 'slack:deploy:updating' 116 | 'slack:deploy:reverting' 117 | 'slack:deploy:updated' 118 | 'slack:deploy:reverted' 119 | 'slack:deploy:failed' 120 | ``` 121 | 122 | ## Customizing the Messaging 123 | 124 | You can customize the messaging posted to Slack by providing your own messaging 125 | class and overriding several methods. Here is one example: 126 | 127 | ```ruby 128 | if defined?(Slackistrano::Messaging) 129 | module Slackistrano 130 | class CustomMessaging < Messaging::Base 131 | 132 | # Send failed message to #ops. Send all other messages to default channels. 133 | # The #ops channel must exist prior. 134 | def channels_for(action) 135 | if action == :failed 136 | "#ops" 137 | else 138 | super 139 | end 140 | end 141 | 142 | # Suppress starting message. 143 | def payload_for_starting 144 | nil 145 | end 146 | 147 | # Suppress updating message. 148 | def payload_for_updating 149 | nil 150 | end 151 | 152 | # Suppress reverting message. 153 | def payload_for_reverting 154 | nil 155 | end 156 | 157 | # Fancy updated message. 158 | # See https://api.slack.com/docs/message-attachments 159 | def payload_for_updated 160 | { 161 | attachments: [{ 162 | color: 'good', 163 | title: 'Integrations Application Deployed :boom::bangbang:', 164 | fields: [{ 165 | title: 'Environment', 166 | value: stage, 167 | short: true 168 | }, { 169 | title: 'Branch', 170 | value: branch, 171 | short: true 172 | }, { 173 | title: 'Deployer', 174 | value: deployer, 175 | short: true 176 | }, { 177 | title: 'Time', 178 | value: elapsed_time, 179 | short: true 180 | }], 181 | fallback: super[:text] 182 | }], 183 | text: " Application Deployed!" 184 | } 185 | end 186 | 187 | # Default reverted message. Alternatively simply do not redefine this 188 | # method. 189 | def payload_for_reverted 190 | super 191 | end 192 | 193 | # Slightly tweaked failed message. 194 | # See https://api.slack.com/docs/message-formatting 195 | def payload_for_failed 196 | payload = super 197 | payload[:text] = "OMG :fire: #{payload[:text]}" 198 | payload 199 | end 200 | 201 | # Override the deployer helper to pull the best name available (git, password file, env vars). 202 | # See https://github.com/phallstrom/slackistrano/blob/master/lib/slackistrano/messaging/helpers.rb 203 | def deployer 204 | name = `git config user.name`.strip 205 | name = nil if name.empty? 206 | name ||= Etc.getpwnam(ENV['USER']).gecos || ENV['USER'] || ENV['USERNAME'] 207 | name 208 | end 209 | end 210 | end 211 | end 212 | ``` 213 | 214 | The output would look like this: 215 | ![Custom Messaging](https://raw.githubusercontent.com/phallstrom/slackistrano/overhaul/images/custom_messaging.jpg) 216 | 217 | To set this up: 218 | 219 | 1. Add the above class to your app, for example `lib/custom_messaging.rb`. 220 | 221 | 2. Require the library after the requiring of Slackistrano in your application's Capfile. 222 | 223 | ```ruby 224 | require_relative 'lib/custom_messaging' 225 | ``` 226 | 227 | 3. Update the `slackistrano` configuration in `config/deploy.rb` and add the `klass` option. 228 | 229 | ```ruby 230 | set :slackistrano, { 231 | klass: Slackistrano::CustomMessaging, 232 | channel: '#your-channel', 233 | webhook: 'your-incoming-webhook-url' 234 | } 235 | ``` 236 | 237 | 4. If you come up with something that you think others would enjoy submit it as 238 | an issue along with a screenshot of the output from `cap production 239 | slack:deploy:test` and I'll add it to the Wiki. 240 | 241 | ## Disabling posting to Slack 242 | 243 | You can disable deployment notifications to a specific stage by setting the `:slackistrano` 244 | configuration variable to `false` instead of actual settings. 245 | 246 | ```ruby 247 | set :slackistrano, false 248 | ``` 249 | 250 | ## TODO 251 | 252 | - Notify about incorrect configuration settings. 253 | 254 | ## Contributing 255 | 256 | 1. Fork it 257 | 2. Create your feature branch (`git checkout -b my-new-feature`) 258 | 3. Commit your changes (`git commit -am 'Add some feature'`) 259 | 4. Push to the branch (`git push origin my-new-feature`) 260 | 5. Create new Pull Request 261 | --------------------------------------------------------------------------------