capistrano-slacky 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +13 -0
- data/LICENSE.md +21 -0
- data/README.md +13 -0
- data/lib/capistrano-slacky.rb +3 -0
- data/lib/capistrano/slacky.rb +36 -0
- data/lib/capistrano/slacky/block.rb +6 -0
- data/lib/capistrano/slacky/block/context.rb +35 -0
- data/lib/capistrano/slacky/block/header.rb +24 -0
- data/lib/capistrano/slacky/block/root.rb +19 -0
- data/lib/capistrano/slacky/block/section.rb +34 -0
- data/lib/capistrano/slacky/command.rb +4 -0
- data/lib/capistrano/slacky/command/diff.rb +79 -0
- data/lib/capistrano/slacky/command/duration.rb +13 -0
- data/lib/capistrano/slacky/configuration.rb +74 -0
- data/lib/capistrano/slacky/facade.rb +9 -0
- data/lib/capistrano/slacky/facade/body.rb +35 -0
- data/lib/capistrano/slacky/facade/changelog.rb +45 -0
- data/lib/capistrano/slacky/facade/deployed_by.rb +15 -0
- data/lib/capistrano/slacky/facade/exception.rb +23 -0
- data/lib/capistrano/slacky/facade/header.rb +47 -0
- data/lib/capistrano/slacky/facade/revision.rb +31 -0
- data/lib/capistrano/slacky/facade/root.rb +9 -0
- data/lib/capistrano/slacky/fanout.rb +39 -0
- data/lib/capistrano/slacky/i18n.rb +21 -0
- data/lib/capistrano/slacky/messaging.rb +22 -0
- data/lib/capistrano/slacky/messaging/base.rb +37 -0
- data/lib/capistrano/slacky/messaging/default.rb +37 -0
- data/lib/capistrano/slacky/messaging/null.rb +21 -0
- data/lib/capistrano/slacky/payload.rb +25 -0
- data/lib/capistrano/slacky/runner.rb +18 -0
- data/lib/capistrano/slacky/version.rb +7 -0
- data/lib/tasks/slacky.rake +42 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c87234b5cfdadfb8ce089157537abe447d9836f10bb53b457a2e56dbd8a43f39
|
4
|
+
data.tar.gz: 8c80128b0c46a4b1e7882a8c13cdb68d627054d341605148eeb88964cd43dd63
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a4a123f09d2ea1285546071859f1bd2fa7d2a86b100e435f580b410a9e348f540fd2717769a8b3fff7862ade8c9aa62b8973c1b971b8f867f9793ee5b942c89f
|
7
|
+
data.tar.gz: 6918fccca963085fc524885ff4adee5cc8fa5b28723417f60a6fcdfa049728c04eb079458268d0f86dd4b7e8067030500ba11ff3be48535bbecc21ce78b34467
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
|
9
|
+
## [0.1.0] - 2021-05-26
|
10
|
+
### Added
|
11
|
+
- Initial version. ([@chubchenko][])
|
12
|
+
|
13
|
+
[@chubchenko]: https://github.com/chubchenko
|
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Artem Chubchenko
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
[![build][1]][2]
|
2
|
+
|
3
|
+
# capistrano-slacky
|
4
|
+
|
5
|
+
Send `Capistrano` deployment status to `Slack`.
|
6
|
+
|
7
|
+
## License
|
8
|
+
|
9
|
+
[MIT][3]
|
10
|
+
|
11
|
+
[1]: https://github.com/chubchenko/capistrano-slacky/workflows/build/badge.svg
|
12
|
+
[2]: https://github.com/chubchenko/capistrano-slacky/actions
|
13
|
+
[3]: https://choosealicense.com/licenses/mit
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
load File.expand_path("../../tasks/slacky.rake", __FILE__)
|
4
|
+
|
5
|
+
require_relative "slacky/version"
|
6
|
+
require_relative "slacky/configuration"
|
7
|
+
require_relative "slacky/i18n"
|
8
|
+
require_relative "slacky/runner"
|
9
|
+
require_relative "slacky/block"
|
10
|
+
require_relative "slacky/facade"
|
11
|
+
require_relative "slacky/command"
|
12
|
+
|
13
|
+
module Capistrano
|
14
|
+
module Slacky
|
15
|
+
module_function
|
16
|
+
|
17
|
+
# Delegate any missing method call to the configuration
|
18
|
+
def method_missing(method_name, *arguments, &block)
|
19
|
+
return super unless configuration.respond_to?(method_name)
|
20
|
+
|
21
|
+
configuration.public_send(method_name, *arguments, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Replace the Object.respond_to?() method
|
25
|
+
def respond_to_missing?(method_name, include_private = false)
|
26
|
+
configuration.respond_to?(method_name) || super
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Capistrano::Slacky::Configuration]
|
30
|
+
def configuration
|
31
|
+
@configuration ||= ::Capistrano::Slacky::Configuration.new
|
32
|
+
end
|
33
|
+
|
34
|
+
private_class_method :configuration
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Block
|
6
|
+
class Context
|
7
|
+
def initialize(*elements)
|
8
|
+
@elements = elements.map do |element|
|
9
|
+
Element.new(element)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_json
|
14
|
+
{
|
15
|
+
type: :context,
|
16
|
+
elements: @elements.map(&:to_json)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
class Element
|
21
|
+
def initialize(text)
|
22
|
+
@text = text
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_json
|
26
|
+
{
|
27
|
+
type: :mrkdwn,
|
28
|
+
text: @text
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Block
|
6
|
+
class Header
|
7
|
+
def initialize(text)
|
8
|
+
@text = text
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_json
|
12
|
+
{
|
13
|
+
type: :header,
|
14
|
+
text: {
|
15
|
+
type: :plain_text,
|
16
|
+
text: @text,
|
17
|
+
emoji: true
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Block
|
6
|
+
class Root
|
7
|
+
def initialize(*blocks)
|
8
|
+
@blocks = blocks.flatten
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_json
|
12
|
+
{
|
13
|
+
blocks: @blocks.map(&:to_json)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Block
|
6
|
+
class Section
|
7
|
+
def initialize(*fields)
|
8
|
+
@fields = fields.map do |field|
|
9
|
+
Field.new(field)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_json
|
14
|
+
{
|
15
|
+
type: :section,
|
16
|
+
fields: @fields.map(&:to_json)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
class Field
|
21
|
+
def initialize(text)
|
22
|
+
@text = text
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_json
|
26
|
+
{
|
27
|
+
type: :mrkdwn, text: @text
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Command
|
6
|
+
class Diff
|
7
|
+
def self.call(previous:, current:)
|
8
|
+
new(previous: previous, current: current).call
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(previous:, current:)
|
12
|
+
@previous = previous
|
13
|
+
@current = current
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
diff = ::IO.popen(
|
18
|
+
["git", "log", "--oneline", "--first-parent", "#{previous}..#{current}"]
|
19
|
+
).readlines
|
20
|
+
|
21
|
+
diff.map.with_index(1) do |line, index|
|
22
|
+
sha, commit = line.match(/^(\w+) (.*+?)/).captures
|
23
|
+
|
24
|
+
if /^Merge pull request/.match?(commit)
|
25
|
+
commit = ::IO.popen(["git", "log", "-1", sha, '--pretty=format:"%b"']).readline
|
26
|
+
end
|
27
|
+
|
28
|
+
Message.new(index: index, sha: sha, commit: commit)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :previous, :current
|
35
|
+
end
|
36
|
+
|
37
|
+
class Message
|
38
|
+
EMOJI_MAP = {
|
39
|
+
"1" => ":one:",
|
40
|
+
"2" => ":two:",
|
41
|
+
"3" => ":three:",
|
42
|
+
"4" => ":four:",
|
43
|
+
"5" => ":five:",
|
44
|
+
"6" => ":six:",
|
45
|
+
"7" => ":seven:",
|
46
|
+
"8" => ":eight:",
|
47
|
+
"9" => ":nine:",
|
48
|
+
"0" => ":zero:"
|
49
|
+
}.freeze
|
50
|
+
|
51
|
+
def initialize(index:, sha:, commit:)
|
52
|
+
@index = index
|
53
|
+
@sha = sha
|
54
|
+
@commit = commit
|
55
|
+
end
|
56
|
+
|
57
|
+
def emoji
|
58
|
+
@index.to_s.chars.map do |value|
|
59
|
+
EMOJI_MAP[value]
|
60
|
+
end.join
|
61
|
+
end
|
62
|
+
|
63
|
+
def link
|
64
|
+
"<#{::Capistrano::Slacky.repo.url}/commit/#{@sha}|#{@sha}>"
|
65
|
+
end
|
66
|
+
|
67
|
+
def commit
|
68
|
+
@commit.delete('"').strip
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_a
|
72
|
+
[emoji, link, commit]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private_constant :Message
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
class Configuration
|
6
|
+
DEFAULT_USERNAME = "ChatOps"
|
7
|
+
DEFAULT_ICON_EMOJI = ":robot_face:"
|
8
|
+
DEFAULT_CHANNEL = "#deployment"
|
9
|
+
|
10
|
+
private_constant :DEFAULT_USERNAME, :DEFAULT_ICON_EMOJI, :DEFAULT_CHANNEL
|
11
|
+
|
12
|
+
def initialize(env: ::Capistrano::Configuration.env)
|
13
|
+
@env = env
|
14
|
+
end
|
15
|
+
|
16
|
+
def username
|
17
|
+
data.fetch(:username, DEFAULT_USERNAME)
|
18
|
+
end
|
19
|
+
|
20
|
+
def icon_emoji
|
21
|
+
data.fetch(:icon_emoji, DEFAULT_ICON_EMOJI)
|
22
|
+
end
|
23
|
+
|
24
|
+
def channel
|
25
|
+
data.fetch(:channel, DEFAULT_CHANNEL)
|
26
|
+
end
|
27
|
+
|
28
|
+
def klass
|
29
|
+
data[:klass]
|
30
|
+
end
|
31
|
+
|
32
|
+
def slacky?
|
33
|
+
return false unless data
|
34
|
+
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def repo
|
39
|
+
@repo ||= Repo.new(
|
40
|
+
remote: env.fetch(:repo_url)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def data
|
47
|
+
@data ||= env.fetch(:slacky, {})
|
48
|
+
end
|
49
|
+
|
50
|
+
class Repo
|
51
|
+
def initialize(remote:)
|
52
|
+
@remote = remote
|
53
|
+
end
|
54
|
+
|
55
|
+
def url
|
56
|
+
return @url if @url
|
57
|
+
|
58
|
+
@url =
|
59
|
+
if ssh?
|
60
|
+
"https://" + @remote[/(?<=@).*/].gsub(".git", "").tr(":", "/")
|
61
|
+
else
|
62
|
+
@remote
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def ssh?
|
67
|
+
@remote.match?(/((git|ssh|http(s)?)|(git@[\w.]+))(:(\/)?)([\w.@:\/-~]+)(\.git)(\/)?/)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private_constant :Repo
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "facade/root"
|
4
|
+
require_relative "facade/header"
|
5
|
+
require_relative "facade/body"
|
6
|
+
require_relative "facade/revision"
|
7
|
+
require_relative "facade/changelog"
|
8
|
+
require_relative "facade/exception"
|
9
|
+
require_relative "facade/deployed_by"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Facade
|
6
|
+
class Body < ::Capistrano::Slacky::Block::Section
|
7
|
+
def initialize(env:)
|
8
|
+
@env = env
|
9
|
+
|
10
|
+
super(
|
11
|
+
t("slacky.stage"), "`#{stage}`",
|
12
|
+
t("slacky.branch"), "`#{branch}`",
|
13
|
+
t("slacky.duration"), "`#{duration}`",
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :env
|
20
|
+
|
21
|
+
def stage
|
22
|
+
@stage ||= env.fetch(:stage)
|
23
|
+
end
|
24
|
+
|
25
|
+
def branch
|
26
|
+
@branch ||= env.fetch(:branch)
|
27
|
+
end
|
28
|
+
|
29
|
+
def duration
|
30
|
+
@duration ||= ::Capistrano::Slacky::Command::Duration.call
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Facade
|
6
|
+
class Changelog
|
7
|
+
def self.for(env:)
|
8
|
+
new(
|
9
|
+
previous: env.fetch(:previous_revision),
|
10
|
+
current: env.fetch(:current_revision)
|
11
|
+
).call
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :difference
|
15
|
+
|
16
|
+
def initialize(previous:, current:)
|
17
|
+
ref = self
|
18
|
+
|
19
|
+
on(::Capistrano::Configuration.env.primary(:app)) do
|
20
|
+
within repo_path do
|
21
|
+
ref.difference = ::Capistrano::Slacky::Command::Diff.call(
|
22
|
+
previous: previous,
|
23
|
+
current: current
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def call
|
30
|
+
if difference.empty?
|
31
|
+
return ::Capistrano::Slacky::Block::Context.new(
|
32
|
+
t("slacky.nothing_has_changed_since_the_previous_release")
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
difference.map do |message|
|
37
|
+
::Capistrano::Slacky::Block::Context.new(
|
38
|
+
*message.to_a
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Facade
|
6
|
+
class DeployedBy < ::Capistrano::Slacky::Block::Context
|
7
|
+
def initialize(env:)
|
8
|
+
super(
|
9
|
+
t("slacky.deployed_by", deployer: env.fetch(:local_user))
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Facade
|
6
|
+
class Exception < ::Capistrano::Slacky::Block::Context
|
7
|
+
DefaultException = ::Class.new(::StandardError) do
|
8
|
+
def initialize
|
9
|
+
super(t("slacky.something_went_wrong"))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private_constant :DefaultException
|
14
|
+
|
15
|
+
def initialize(exception: $ERROR_INFO)
|
16
|
+
super(
|
17
|
+
(exception || DefaultException.new).message
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Facade
|
6
|
+
class Header < ::Capistrano::Slacky::Block::Header
|
7
|
+
DEPLOYED_SUCCESSFULLY = [
|
8
|
+
":smile:",
|
9
|
+
":drooling_face:",
|
10
|
+
":sunglasses:",
|
11
|
+
":partying_face:",
|
12
|
+
":heart_eyes:"
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
REVERTED_SUCCESSFULLY = [
|
16
|
+
":upside_down_face:",
|
17
|
+
":pensive:",
|
18
|
+
":face_with_raised_eyebrow:",
|
19
|
+
":worried:"
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
DEPLOYMENT_FAILED = [
|
23
|
+
":grimacing:",
|
24
|
+
":face_with_head_bandage:",
|
25
|
+
":face_with_thermometer:",
|
26
|
+
":woozy_face:",
|
27
|
+
":exploding_head:",
|
28
|
+
":sob:",
|
29
|
+
":cry:"
|
30
|
+
].freeze
|
31
|
+
|
32
|
+
ROLLBACK_FAILED = DEPLOYMENT_FAILED
|
33
|
+
|
34
|
+
EMOJI_MAP = {
|
35
|
+
deployed_successfully: DEPLOYED_SUCCESSFULLY,
|
36
|
+
reverted_successfully: REVERTED_SUCCESSFULLY,
|
37
|
+
deployment_failed: DEPLOYMENT_FAILED,
|
38
|
+
rollback_failed: ROLLBACK_FAILED
|
39
|
+
}.freeze
|
40
|
+
|
41
|
+
def initialize(text)
|
42
|
+
super(t("slacky.#{text}", emoji: EMOJI_MAP.fetch(text).sample))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Facade
|
6
|
+
class Revision < ::Capistrano::Slacky::Block::Context
|
7
|
+
def initialize(env:)
|
8
|
+
@env = env
|
9
|
+
|
10
|
+
super(
|
11
|
+
t("slacky.revision", repository_url: ::Capistrano::Slacky.repo.url,
|
12
|
+
current: current,
|
13
|
+
previous: previous)
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :env
|
20
|
+
|
21
|
+
def current
|
22
|
+
env.fetch(:current_revision)[0, 7]
|
23
|
+
end
|
24
|
+
|
25
|
+
def previous
|
26
|
+
env.fetch(:previous_revision)[0, 7]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Hook
|
6
|
+
require "uri"
|
7
|
+
|
8
|
+
DEFAULT_HOOK_FILE = "config/slacky.yml"
|
9
|
+
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def uri(role: Hook.role)
|
13
|
+
output = nil
|
14
|
+
|
15
|
+
on(role) do
|
16
|
+
output = capture(:cat, File.join(shared_path, DEFAULT_HOOK_FILE))
|
17
|
+
end
|
18
|
+
|
19
|
+
URI(output)
|
20
|
+
end
|
21
|
+
|
22
|
+
def role
|
23
|
+
::Capistrano::Configuration.env.primary(:app)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private_constant :Hook
|
28
|
+
|
29
|
+
class Fanout
|
30
|
+
require "net/http"
|
31
|
+
|
32
|
+
def self.call(payload:, hook: Hook)
|
33
|
+
::Net::HTTP.post_form(
|
34
|
+
hook.uri, payload: payload.to_json
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
en = {
|
4
|
+
deployed_successfully: "Deployed successfully %{emoji}",
|
5
|
+
reverted_successfully: "Reverted successfully %{emoji}",
|
6
|
+
deployment_failed: "Deployment failed %{emoji}",
|
7
|
+
rollback_failed: "Rollback failed %{emoji}",
|
8
|
+
stage: ">*Stage:*",
|
9
|
+
branch: ">*Branch:*",
|
10
|
+
duration: ">*Duration:*",
|
11
|
+
revision: "*Current* revision <%{repository_url}/commit/%{current}|%{current}> - " \
|
12
|
+
"<%{repository_url}/compare/%{previous}...%{current}|compare> " \
|
13
|
+
"(_previous revision was <%{repository_url}/commit/%{previous}|%{previous}>_).",
|
14
|
+
nothing_has_changed_since_the_previous_release: "Nothing has changed since the previous release :confounded:.",
|
15
|
+
something_went_wrong: "Something went wrong and we couldn't get the exception to display :sob:.",
|
16
|
+
deployed_by: ":truck: Deployed By: @%{deployer}"
|
17
|
+
}
|
18
|
+
|
19
|
+
I18n.backend.store_translations(:en, capistrano: {
|
20
|
+
slacky: en
|
21
|
+
})
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "messaging/base"
|
4
|
+
require_relative "messaging/default"
|
5
|
+
require_relative "messaging/null"
|
6
|
+
|
7
|
+
module Capistrano
|
8
|
+
module Slacky
|
9
|
+
module Messaging
|
10
|
+
def self.for(env:)
|
11
|
+
klass =
|
12
|
+
if ::Capistrano::Slacky.slacky?
|
13
|
+
(::Capistrano::Slacky.klass || Default)
|
14
|
+
else
|
15
|
+
Null
|
16
|
+
end
|
17
|
+
|
18
|
+
klass.new(env: env)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Messaging
|
6
|
+
UnpermittedAction = Class.new(StandardError) do
|
7
|
+
def initialize(action:)
|
8
|
+
super("Unpermitted action: #{action}.")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Base
|
13
|
+
PERMITTED_ACTIONS = [
|
14
|
+
:updated,
|
15
|
+
:reverted,
|
16
|
+
:failed
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
private_constant :PERMITTED_ACTIONS
|
20
|
+
|
21
|
+
def initialize(env:)
|
22
|
+
@env = env
|
23
|
+
end
|
24
|
+
|
25
|
+
def payload_for(action:)
|
26
|
+
raise UnpermittedAction.new(action: action) unless PERMITTED_ACTIONS.include?(action)
|
27
|
+
|
28
|
+
public_send("payload_for_#{action}")
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :env
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Messaging
|
6
|
+
class Default < Base
|
7
|
+
def payload_for_updated
|
8
|
+
::Capistrano::Slacky::Facade::Root.new(
|
9
|
+
::Capistrano::Slacky::Facade::Header.new(:deployed_successfully),
|
10
|
+
::Capistrano::Slacky::Facade::Body.new(env: env),
|
11
|
+
::Capistrano::Slacky::Facade::Revision.new(env: env),
|
12
|
+
::Capistrano::Slacky::Facade::Changelog.for(env: env),
|
13
|
+
::Capistrano::Slacky::Facade::DeployedBy.new(env: env)
|
14
|
+
).to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
def payload_for_reverted
|
18
|
+
::Capistrano::Slacky::Facade::Root.new(
|
19
|
+
::Capistrano::Slacky::Facade::Header.new(:reverted_successfully),
|
20
|
+
::Capistrano::Slacky::Facade::Body.new(env: env),
|
21
|
+
::Capistrano::Slacky::Facade::Revision.new(env: env),
|
22
|
+
::Capistrano::Slacky::Facade::DeployedBy.new(env: env)
|
23
|
+
).to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
def payload_for_failed
|
27
|
+
::Capistrano::Slacky::Facade::Root.new(
|
28
|
+
::Capistrano::Slacky::Facade::Header.new(deploying? ? :deployment_failed : :rollback_failed),
|
29
|
+
::Capistrano::Slacky::Facade::Body.new(env: env),
|
30
|
+
::Capistrano::Slacky::Facade::Exception.new,
|
31
|
+
::Capistrano::Slacky::Facade::DeployedBy.new(env: env)
|
32
|
+
).to_json
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Slacky
|
5
|
+
module Messaging
|
6
|
+
class Null < Base
|
7
|
+
def payload_for_updated
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def payload_for_reverted
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def payload_for_failed
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "messaging"
|
4
|
+
|
5
|
+
module Capistrano
|
6
|
+
module Slacky
|
7
|
+
class Payload
|
8
|
+
def initialize(env:, action:)
|
9
|
+
@env = env
|
10
|
+
@action = action
|
11
|
+
@messaging = ::Capistrano::Slacky::Messaging.for(env: env)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_json
|
15
|
+
{
|
16
|
+
username: ::Capistrano::Slacky.username,
|
17
|
+
icon_emoji: ::Capistrano::Slacky.icon_emoji,
|
18
|
+
channel: ::Capistrano::Slacky.channel
|
19
|
+
}.merge(
|
20
|
+
@messaging.payload_for(action: @action)
|
21
|
+
).to_json
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "payload"
|
4
|
+
require_relative "fanout"
|
5
|
+
|
6
|
+
module Capistrano
|
7
|
+
module Slacky
|
8
|
+
class Runner
|
9
|
+
def self.call(env:, action:)
|
10
|
+
payload = ::Capistrano::Slacky::Payload.new(
|
11
|
+
env: env, action: action
|
12
|
+
)
|
13
|
+
|
14
|
+
::Capistrano::Slacky::Fanout.call(payload: payload)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :slacky do
|
4
|
+
desc "Slacky after a successful deployment"
|
5
|
+
task :updated do
|
6
|
+
Capistrano::Slacky::Runner.call(
|
7
|
+
env: self, action: :updated
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Slacky after successful rollback"
|
12
|
+
task :reverted do
|
13
|
+
Capistrano::Slacky::Runner.call(
|
14
|
+
env: self, action: :reverted
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Slacky after failure deployment or rollback"
|
19
|
+
task :failed do
|
20
|
+
Capistrano::Slacky::Runner.call(
|
21
|
+
env: self, action: :failed
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
# task :ping do
|
26
|
+
# [:updated, :reverted, :failed].each do |action|
|
27
|
+
# Capistrano::Slacky::Runner.call(
|
28
|
+
# env: self, action: action
|
29
|
+
# )
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
end
|
33
|
+
|
34
|
+
before :'deploy:finishing', :'slacky:updated'
|
35
|
+
before :'deploy:finishing_rollback', :'slacky:reverted'
|
36
|
+
before :'deploy:failed', :'slacky:failed'
|
37
|
+
|
38
|
+
namespace :load do
|
39
|
+
task :defaults do
|
40
|
+
append :linked_files, "config/slacky.yml"
|
41
|
+
end
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capistrano-slacky
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Artem Chubchenko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-05-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: capistrano
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.0.0
|
20
|
+
- - "~>"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.0.0
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '13.0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '13.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.10'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.10'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: simplecov
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0.21'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0.21'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: webmock
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.12'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.12'
|
89
|
+
description: Send Capistrano deployment status to Slack via the incoming webhooks
|
90
|
+
integration
|
91
|
+
email: artem.chubchenko@gmail.com
|
92
|
+
executables: []
|
93
|
+
extensions: []
|
94
|
+
extra_rdoc_files: []
|
95
|
+
files:
|
96
|
+
- CHANGELOG.md
|
97
|
+
- LICENSE.md
|
98
|
+
- README.md
|
99
|
+
- lib/capistrano-slacky.rb
|
100
|
+
- lib/capistrano/slacky.rb
|
101
|
+
- lib/capistrano/slacky/block.rb
|
102
|
+
- lib/capistrano/slacky/block/context.rb
|
103
|
+
- lib/capistrano/slacky/block/header.rb
|
104
|
+
- lib/capistrano/slacky/block/root.rb
|
105
|
+
- lib/capistrano/slacky/block/section.rb
|
106
|
+
- lib/capistrano/slacky/command.rb
|
107
|
+
- lib/capistrano/slacky/command/diff.rb
|
108
|
+
- lib/capistrano/slacky/command/duration.rb
|
109
|
+
- lib/capistrano/slacky/configuration.rb
|
110
|
+
- lib/capistrano/slacky/facade.rb
|
111
|
+
- lib/capistrano/slacky/facade/body.rb
|
112
|
+
- lib/capistrano/slacky/facade/changelog.rb
|
113
|
+
- lib/capistrano/slacky/facade/deployed_by.rb
|
114
|
+
- lib/capistrano/slacky/facade/exception.rb
|
115
|
+
- lib/capistrano/slacky/facade/header.rb
|
116
|
+
- lib/capistrano/slacky/facade/revision.rb
|
117
|
+
- lib/capistrano/slacky/facade/root.rb
|
118
|
+
- lib/capistrano/slacky/fanout.rb
|
119
|
+
- lib/capistrano/slacky/i18n.rb
|
120
|
+
- lib/capistrano/slacky/messaging.rb
|
121
|
+
- lib/capistrano/slacky/messaging/base.rb
|
122
|
+
- lib/capistrano/slacky/messaging/default.rb
|
123
|
+
- lib/capistrano/slacky/messaging/null.rb
|
124
|
+
- lib/capistrano/slacky/payload.rb
|
125
|
+
- lib/capistrano/slacky/runner.rb
|
126
|
+
- lib/capistrano/slacky/version.rb
|
127
|
+
- lib/tasks/slacky.rake
|
128
|
+
homepage: https://github.com/chubchenko/capistrano-slacky
|
129
|
+
licenses:
|
130
|
+
- MIT
|
131
|
+
metadata:
|
132
|
+
bug_tracker_uri: https://github.com/chubchenko/capistrano-slacky/issues
|
133
|
+
changelog_uri: https://github.com/chubchenko/capistrano-slacky/blob/master/CHANGELOG.md
|
134
|
+
homepage_uri: https://github.com/chubchenko/capistrano-slacky
|
135
|
+
source_code_uri: https://github.com/chubchenko/capistrano-slacky
|
136
|
+
github_repo: ssh://github.com/chubchenko/capistrano-slacky
|
137
|
+
post_install_message:
|
138
|
+
rdoc_options: []
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 2.5.0
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
requirements: []
|
152
|
+
rubygems_version: 3.0.8
|
153
|
+
signing_key:
|
154
|
+
specification_version: 4
|
155
|
+
summary: Send Capistrano deployment status to Slack
|
156
|
+
test_files: []
|