Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ spec/reports
test/tmp
test/version_tmp
tmp
.ruby-version
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ source 'https://rubygems.org'

# Specify your gem's dependencies in mina-slack.gemspec
gemspec

42 changes: 28 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,52 @@ Announce Mina deployments to a slack channel.

Add this line to your application's Gemfile:

gem 'mina-slack'
gem 'mina-slack', github: "eManPrague/mina-slack"

And then execute:

$ bundle

Or install it yourself as:

$ gem install mina-slack

## Usage

### Load the recipe
Include the recipe in your deploy.rb

# config/deploy.rb
require 'mina/slack/tasks'
require 'mina/slack'

### Setup Slack Details
You'll need to setup your slack details with an API key, room and subdomain. You can add these as ENV variables or in the deploy.rb
### Setup Mina Slack
You'll need to setup your slack details with an API key, room and subdomain. You can add these as ENV variables or in the config/deploy.rb

# config/deploy.rb
set :slack_token, 'SLACK_API_KEY'
set :slack_room, '#slack_room'
set :slack_subdomain, 'slack_subdomain'
# required
set :slack_url, 'https://hooks.slack.com/services/<YOUR-STRING1>/<YOUR-STRING2>' # comes from inbound webhook integration
set :slack_room, '#general' # the room to send the message to

# optional
set :slack_application, Application name
set :slack_username, 'Deploy Bot' # displayed as name of message sender
set :slack_emoji, ':cloud:' # will be used as the avatar for the message
set :slack_stage, 'staging' # will be used to specify the deployment environment

Or use the ENV variables:

ENV['SLACK_TOKEN'] = ''
# required
ENV['SLACK_URL'] = ''
ENV['SLACK_ROOM'] = ''
ENV['SLACK_SUBDOMAIN'] = ''

# optional
ENV['SLACK_APPLICATION'] = ''
ENV['SLACK_USERNAME'] = ''
ENV['SLACK_EMOJI'] = ''
ENV['SLACK_STAGE'] = '' # or ENV['to']

Update `deploy` task to invoke `slack:post_info` task:

```ruby
task :deploy do
invoke :'slack:post_info'
end
```

## Contributing

Expand Down
149 changes: 149 additions & 0 deletions lib/mina/helpers/internal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
module Mina
module Helpers
module Internal
include Helpers::Output

def deploy_script
yield
erb Mina.root_path(fetch(:deploy_script))
end

def erb(file, b = binding)
require 'erb'
erb = ERB.new(File.read(file))
erb.result b
end

def echo_cmd(code, ignore_verbose = false)
if fetch(:verbose) && !ignore_verbose
"echo #{Shellwords.escape('$ ' + code)} &&\n#{code}"
else
code
end
end

def indent(count, str)
str.gsub(/^/, ' ' * count)
end

def unindent(code)
if code =~ /^\n([ \t]+)/
code = code.gsub(/^#{$1}/, '')
end

code.strip
end

def report_time
time_start = Time.now
output = yield
print_info "Elapsed time: %.2f seconds" % [Time.now - time_start]
output
end

def next_version
case fetch(:version_scheme)
when :datetime
Time.now.utc.strftime("%Y%m%d%H%M%S")
when :sequence
"$((`ls -1 #{fetch(:releases_path)} | sort -n | tail -n 1`+1))"
else
error! 'Unrecognizes version scheme. Use :datetime or :sequence'
end
end

def error!(message)
url = fetch(:slack_url)
send_slack_message(slack_deploy_fail_message, url) if (url && fetch(:slack_room))
print_error message
exit 1
end

def slack_deploy_message
attachment = {
fallback: 'Required plain-text summary of the attachment.',
color: '#36a64f',
fields: [attachment_project, attachment_enviroment, attachment_deployer, attachment_revision, attachment_changes]
}

message = {
'parse' => 'full',
'channel' => fetch(:slack_room),
'username' => fetch(:slack_username),
'attachments' => [attachment],
'icon_emoji' => fetch(:slack_emoji)
}
end

def slack_deploy_fail_message
attachment = {
fallback: 'Required plain-text summary of the attachment.',
color: '#ff3300',
fields: [attachment_deploy_failed]
}

message = {
'parse' => 'full',
'channel' => fetch(:slack_room),
'username' => fetch(:slack_username),
'attachments' => [attachment],
'icon_emoji' => fetch(:slack_emoji)
}
end

def send_slack_message(message, slack_url)
uri = URI.parse(slack_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data(payload: message.to_json)

http.request(request)
rescue Encoding::InvalidByteSequenceError
comment 'Invalid byte sequence'
end

def short_revision
deployed_revision = fetch(:last_commit)
deployed_revision[0..8] if deployed_revision
end

def attachment_project
{ title: 'New version of project', value: fetch(:slack_application), short: true }
end

def attachment_enviroment
{ title: 'Environment', value: fetch(:slack_stage), short: true }
end

def attachment_deployer
{ title: 'Deployer', value: fetch(:deployer), short: true }
end

def attachment_revision
{ title: 'Revision', value: "#{fetch(:slack_application)}: #{fetch(:slack_stage)} #{short_revision}", short: true }
end

def attachment_changes
{ title: 'Changes', value: fetch(:changes), short: false }
end

def attachment_deploy_failed
{ title: 'Deploy status', value: "Deployment of #{fetch(:slack_application)} has failed.", short: false}
end

def changes
last_revision = fetch(:last_revision)
if last_revision.empty?
set(:changes, `git --no-pager log --pretty=format:'Commit: %h - %ad%n%an - %s%n' --date=short --abbrev-commit #{fetch(:deployed_revision)} origin/#{fetch(:branch)} --`)
else
set(:changes, `git --no-pager log --pretty=format:'Commit: %h - %ad%n%an - %s%n' --date=short --abbrev-commit #{fetch(:last_revision)}...#{fetch(:last_commit)} origin/#{fetch(:branch)} --`)
end
end

end
end
end
extend Mina::Helpers::Internal
59 changes: 59 additions & 0 deletions lib/mina/helpers/output.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module Mina
module Helpers
module Output
def print_line(line)
line.scrub!
case line
when /^\-+> (.*?)$/
print_status Regexp.last_match[1]
when /^! (.*?)$/
print_error Regexp.last_match[1]
when /^\$ (.*?)$/
print_command Regexp.last_match[1]
else
print_stdout line
end
end

def print_status(msg)
msg.scrub!
puts "#{color('----->', 32)} #{msg}"
end

def print_error(msg)
msg.scrub!
puts " #{color('!', 33)} #{color(msg, 31)}"
end

def print_stderr(msg)
msg.scrub!
if msg =~ /I, \[/ # fix for asset precompile
print_stdout msg
else
puts " #{color(msg, 31)}"
end
end

def print_command(msg)
msg.scrub!
puts " #{color('$', 36)} #{color(msg, 36)}"
end

def print_info(msg)
msg.scrub!
puts " #{color(msg, 96)}"
end

def print_stdout(msg)
msg.scrub!
puts " #{msg}"
end

def color(str, c)
ENV['NO_COLOR'] ? str : "\033[#{c}m#{str}\033[0m"
end
end
end
end

extend Mina::Helpers::Output
7 changes: 6 additions & 1 deletion lib/mina/slack.rb
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
require 'mina/slack/version'
require 'mina/slack/defaults'
require 'mina/slack/tasks'
require 'json'
require 'net/http'
require 'openssl'
require 'net/ssh'
16 changes: 10 additions & 6 deletions lib/mina/slack/defaults.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
set :deployer, ENV['GIT_AUTHOR_NAME'] || `git config user.name`.chomp
set :announced_stage, ENV['to'] || 'production'

set :slack_token, ENV['SLACK_TOKEN']
set :slack_room, ENV['SLACK_ROOM']
set :slack_subdomain, ENV['SLACK_SUBDOMAIN']
# Required
set :slack_url, -> { ENV['SLACK_URL'] }
set :slack_room, -> { ENV['SLACK_ROOM'] }
# Optional
set :slack_stage, -> { ENV['SLACK_STAGE'] || ENV['TO'] || ENV['to'] || fetch(:rails_env) }
set :slack_application, -> { ENV['SLACK_APPLICATION'] || application }
set :slack_username, -> { ENV['SLACK_USERNAME'] || 'deploybot' }
set :slack_emoji, -> { ENV['SLACK_EMOJI'] || ':cloud:' }
# Git
set :deployer, -> { ENV['GIT_AUTHOR_NAME'] || %x[git config user.name].chomp }
76 changes: 18 additions & 58 deletions lib/mina/slack/tasks.rb
Original file line number Diff line number Diff line change
@@ -1,63 +1,23 @@
require 'mina/hooks'

require 'json'
require 'net/http'

require 'mina/slack/defaults'



# Before and after hooks for mina deploy
before_mina :deploy, :'slack:starting'
after_mina :deploy, :'slack:finished'


# Slack tasks
namespace :slack do

task :starting do
if slack_token and slack_room and slack_subdomain
announced_stage = ENV['to'] || rails_env || 'production'
announcement = "#{deployer} is deploying #{app}'s #{branch} to #{announced_stage}"

# Parse the API url and create an SSL connection
uri = URI.parse("https://#{slack_subdomain}.slack.com/services/hooks/incoming-webhook?token=#{slack_token}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

# Create the post request and setup the form data
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data(payload: {channel: slack_room, username: 'deploybot', text: announcement, icon_emoji: ':ghost:'}.to_json)

# Make the actual request to the API
response = http.request(request)
else
print_local_status "Unable to create Slack Announcement, no slack details provided."
end
end



task :finished do
if slack_token and slack_room and slack_subdomain
announced_stage = ENV['to'] || rails_env || 'production'
announcement = "#{deployer} successfully deployed #{app} to #{announced_stage}!"

# Parse the URI and handle the https connection
uri = URI.parse("https://#{slack_subdomain}.slack.com/services/hooks/incoming-webhook?token=#{slack_token}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

# Create the post request and setup the form data
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data(payload: {channel: slack_room, username: 'deploybot', text: announcement, icon_emoji: ':ghost:'}.to_json)

# Make the actual request to the API
response = http.request(request)
task :post_info do
if (url = fetch(:slack_url)) && (room = fetch(:slack_room))
login_data = if (user = fetch(:user))
[ fetch(:domain), user ]
else
# "[email protected]".split('@')
fetch(:domain).split('@').reverse
end

Net::SSH.start(login_data[0], login_data[1]) do |ssh|
set(:last_revision, ssh.exec!("cd #{fetch(:deploy_to)}/scm; git log -n 1 --pretty=format:'%H' #{fetch(:branch)} --"))
end

set(:last_commit, `git log -n 1 --pretty=format:"%H" origin/#{fetch(:branch)} --`)
changes
send_slack_message(slack_deploy_message, url)
else
print_local_status "Unable to create Slack Announcement, no slack details provided."
print_status 'Unable to create Slack Announcement, no slack details provided.'
end
end
end
end
Loading