fett 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +83 -0
- data/lib/fett.rb +2 -0
- data/lib/fett/app.rb +74 -0
- data/lib/fett/build_status_color_handler.rb +13 -0
- data/lib/fett/build_status_message_composer.rb +9 -0
- data/lib/fett/build_status_notifier.rb +11 -0
- data/lib/fett/builder.rb +16 -0
- data/lib/fett/github.rb +35 -0
- data/lib/fett/github_status_message_composer.rb +13 -0
- data/lib/fett/jenkins.rb +23 -0
- data/lib/fett/listener.rb +20 -0
- data/lib/fett/redis_connection.rb +20 -0
- data/lib/fett/repositories.rb +11 -0
- data/lib/fett/repository.rb +23 -0
- data/lib/fett/repository_factory.rb +8 -0
- data/lib/fett/slack.rb +40 -0
- data/lib/fett/template.rb +28 -0
- data/spec/fett/build_status_color_handler.rb +9 -0
- data/spec/fett/build_status_message_composer_spec.rb +30 -0
- data/spec/fett/build_status_notifier_spec.rb +20 -0
- data/spec/fett/builder_spec.rb +23 -0
- data/spec/fett/github_spec.rb +48 -0
- data/spec/fett/github_status_message_composer_spec.rb +12 -0
- data/spec/fett/jenkins_spec.rb +38 -0
- data/spec/fett/listener_spec.rb +25 -0
- data/spec/fett/repositories_spec.rb +39 -0
- data/spec/fett/slack_spec.rb +53 -0
- data/templates/default.xml.erb +60 -0
- metadata +200 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4410cd737f964ba2a93ae57d82565514fac1a3fc
|
4
|
+
data.tar.gz: 86440b24821f49f0b49d792d9a2780a78e3a2f55
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e0a5efb07bf106313698a9d5261b621ec621701d1d784e0fcc8db1950c649e7bfbc19389575b75ba223e7b2764782a7440a7351edee437d73c126113328e07ac
|
7
|
+
data.tar.gz: 9b20e4e8335eaff5cce8fec1940c06e08022f33121ca7bba688dc15614d12db8271c1e43b3e89b7d6ec7118c5e10f2e3aacf12ed75ed115de5978e943c754fa4
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
Fett
|
2
|
+
======
|
3
|
+
|
4
|
+
Fett talks with [GitHub](https://github.com), [Slack](https://slack.com) and [Jenkins](https://jenkins.com) to give you all the information about your builds right in your chat room.
|
5
|
+
|
6
|
+
Installing
|
7
|
+
----------
|
8
|
+
|
9
|
+
* Fett requires [Jenkins](https://wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins), I'm using the 1.574 version but it works on 1.427 too. The [Notification Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Notification+Plugin) is required in order to tell Fett about how last build went.
|
10
|
+
* I've only tested Fett on [Ruby](https://www.ruby-lang.org/es/) 2.1.0. It may work on older versions, but I haven't tested it. Anyways, if you give Fett a shot and get it working in any other version, give me a shout.
|
11
|
+
|
12
|
+
Install the dependencies:
|
13
|
+
|
14
|
+
```bash
|
15
|
+
$ bundle install --binstubs
|
16
|
+
```
|
17
|
+
|
18
|
+
Run the server:
|
19
|
+
|
20
|
+
```bash
|
21
|
+
$ bundle exec rackup -o 0.0.0.0
|
22
|
+
```
|
23
|
+
|
24
|
+
Done :)
|
25
|
+
|
26
|
+
Configuring
|
27
|
+
-----------
|
28
|
+
|
29
|
+
Fett makes heavy use of environment variables (all of them required):
|
30
|
+
|
31
|
+
* `FETT_URL`: The URL where Fett will listen. Eg: https://fett.example.com
|
32
|
+
|
33
|
+
* `FETT_GITHUB_LOGIN`: Any GitHub with access to your repos. Fett will use it to talk to the GitHub API. Eg: pedrogimenez
|
34
|
+
* `FETT_GITHUB_PASSWORD`: The password for the previous GitHub login. Eg: yolo
|
35
|
+
|
36
|
+
|
37
|
+
* `FETT_JENKINS_URL`: The URL where Jenkins is hidden. Eg: https://jenkins.example.com
|
38
|
+
* `FETT_JENKINS_USERNAME`: HTTP Basic Auth user. Eg: pedrogimenez
|
39
|
+
* `FETT_JENKINS_PASSWORD`: HTTP Basic Auth password. Eg: yolo
|
40
|
+
|
41
|
+
|
42
|
+
* `FETT_SLACK_URL`: Your Slack webhook URL. Eg: https://example.slack.com/services/hooks/incoming-webhook?token=xxxx
|
43
|
+
|
44
|
+
|
45
|
+
* `FETT_REDIS_HOST`: The host where Redis is installed. Eg: 127.0.0.1
|
46
|
+
* `FETT_REDIS_PORT`: The port where Redis will be listening. Eg: 6379
|
47
|
+
* `FETT_REDIS_PASSWORD`: Redis Auth Password. Eg: yolo
|
48
|
+
* `FETT_REDIS_DATABASE`: Redis database. Eg: 1
|
49
|
+
|
50
|
+
Hacking
|
51
|
+
-------
|
52
|
+
|
53
|
+
Install the dependencies:
|
54
|
+
|
55
|
+
```bash
|
56
|
+
$ bundle install --binstubs
|
57
|
+
```
|
58
|
+
|
59
|
+
You need to setup the environment variables related to Redis before running the tests:
|
60
|
+
|
61
|
+
* `FETT_REDIS_HOST`
|
62
|
+
* `FETT_REDIS_PORT`
|
63
|
+
* `FETT_REDIS_PASSWORD`
|
64
|
+
* `FETT_REDIS_DATABASE`
|
65
|
+
|
66
|
+
Run the tests:
|
67
|
+
|
68
|
+
```bash
|
69
|
+
$ script/cibuild
|
70
|
+
```
|
71
|
+
|
72
|
+
Run the server:
|
73
|
+
|
74
|
+
```bash
|
75
|
+
$ bundle exec rackup -o 0.0.0.0
|
76
|
+
```
|
77
|
+
|
78
|
+
Done :)
|
79
|
+
|
80
|
+
Contributing
|
81
|
+
------------
|
82
|
+
|
83
|
+
Just fork the repo and send a PR <3
|
data/lib/fett.rb
ADDED
data/lib/fett/app.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require "json"
|
2
|
+
require "sinatra/base"
|
3
|
+
require "sinatra/json"
|
4
|
+
require "octokit"
|
5
|
+
require "jenkins_api_client"
|
6
|
+
|
7
|
+
module Fett
|
8
|
+
class App < Sinatra::Base
|
9
|
+
helpers Sinatra::JSON
|
10
|
+
|
11
|
+
before do
|
12
|
+
Fett::RedisConnection.initialize
|
13
|
+
end
|
14
|
+
|
15
|
+
get "/ping" do
|
16
|
+
"PONG"
|
17
|
+
end
|
18
|
+
|
19
|
+
post "/repositories" do
|
20
|
+
transaction success: 204 do
|
21
|
+
Fett::Listener.listen(params[:repository])
|
22
|
+
headers["location"] = "#{ENV.fetch("FETT_URL")}/builds"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
post "/builds" do
|
27
|
+
transaction success: 204 do
|
28
|
+
halt 400 if %w{ping pull_request}.include?(request.env["HTTP_X_GITHUB_EVENT"])
|
29
|
+
|
30
|
+
response = JSON.parse(request.env["rack.input"].read, symbolize_names: true)
|
31
|
+
|
32
|
+
Fett::Builder.build(
|
33
|
+
response[:repository][:full_name],
|
34
|
+
response[:pusher][:name],
|
35
|
+
response[:head_commit][:message],
|
36
|
+
response[:head_commit][:url],
|
37
|
+
response[:ref],
|
38
|
+
{ GIT_SHA1: response[:head_commit][:id], GIT_BRANCH: response[:ref] }
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
post "/builds/status" do
|
44
|
+
response = JSON.parse(request.env["rack.input"].read, symbolize_names: true)
|
45
|
+
|
46
|
+
status = "pending"
|
47
|
+
|
48
|
+
if response[:build][:status] == "SUCCESS"
|
49
|
+
status = "success"
|
50
|
+
elsif response[:build][:status] == "FAILURE"
|
51
|
+
status = "failure"
|
52
|
+
end
|
53
|
+
|
54
|
+
return unless %w{STARTED FINISHED}.include?(response[:build][:phase])
|
55
|
+
|
56
|
+
Fett::BuildStatusNotifier.notify(response[:name], response[:build][:parameters][:GIT_SHA1], status)
|
57
|
+
end
|
58
|
+
|
59
|
+
def transaction(options, &block)
|
60
|
+
begin
|
61
|
+
status options[:success]
|
62
|
+
|
63
|
+
response = yield
|
64
|
+
|
65
|
+
return if options[:success] == 204
|
66
|
+
|
67
|
+
json response
|
68
|
+
rescue RuntimeError => error
|
69
|
+
status 400
|
70
|
+
json error: error.message
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Fett
|
2
|
+
class BuildStatusMessageComposer
|
3
|
+
def self.compose(repository, status)
|
4
|
+
message = "Build of #{repository.fullname}@<#{repository.last_commit_url}|#{repository.last_branch}> by #{repository.last_pusher}."
|
5
|
+
message += " Well done :sparkles:." if status == "success"
|
6
|
+
message
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Fett
|
2
|
+
class BuildStatusNotifier
|
3
|
+
def self.notify(jenkins_name, commit_sha1, status)
|
4
|
+
fullname = jenkins_name.sub(/\./, "/")
|
5
|
+
repository = Repositories.repository_of_fullname(fullname)
|
6
|
+
|
7
|
+
GitHub.new_build_status(repository, commit_sha1, status)
|
8
|
+
Slack.build_status_message(repository, status)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/fett/builder.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Fett
|
2
|
+
class Builder
|
3
|
+
def self.build(fullname, last_pusher, last_message, last_commit_url, last_branch, params)
|
4
|
+
repository = Repositories.repository_of_fullname(fullname)
|
5
|
+
|
6
|
+
repository.last_pusher = last_pusher
|
7
|
+
repository.last_message = last_message
|
8
|
+
repository.last_commit_url = last_commit_url
|
9
|
+
repository.last_branch = last_branch.split("/").last
|
10
|
+
|
11
|
+
Jenkins.new_build(repository, params)
|
12
|
+
|
13
|
+
Repositories.put(repository)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/fett/github.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Fett
|
2
|
+
class GitHub
|
3
|
+
LOGIN = ENV.fetch("FETT_GITHUB_LOGIN")
|
4
|
+
PASSWORD = ENV.fetch("FETT_GITHUB_PASSWORD")
|
5
|
+
FETT_URL = ENV.fetch("FETT_URL")
|
6
|
+
|
7
|
+
def self.new_hook(repository)
|
8
|
+
client.create_hook(repository.fullname, "web", *params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.new_build_status(repository, commit_sha1, status)
|
12
|
+
message = GitHubStatusMessageComposer.compose(status)
|
13
|
+
client.create_status(repository.fullname, commit_sha1, status, description: message)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def self.client
|
19
|
+
Octokit::Client.new(login: LOGIN, password: PASSWORD)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.params
|
23
|
+
[{
|
24
|
+
url: "#{FETT_URL}/builds",
|
25
|
+
content_type: "json"
|
26
|
+
},
|
27
|
+
{
|
28
|
+
events: ["push"],
|
29
|
+
active: true
|
30
|
+
}]
|
31
|
+
end
|
32
|
+
|
33
|
+
private_class_method :client, :params
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Fett
|
2
|
+
class GitHubStatusMessageComposer
|
3
|
+
def self.compose(status)
|
4
|
+
if status == "success"
|
5
|
+
"I love this code. I love it more than sharks love blood"
|
6
|
+
elsif status == "failure"
|
7
|
+
"My great concern is not whether you have failed, but whether you are content with your failure"
|
8
|
+
else
|
9
|
+
"Don’t count every hour in the day, make every hour in the day count"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/fett/jenkins.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Fett
|
2
|
+
class Jenkins
|
3
|
+
URL = ENV.fetch("FETT_JENKINS_URL")
|
4
|
+
USERNAME = ENV.fetch("FETT_JENKINS_USERNAME")
|
5
|
+
PASSWORD = ENV.fetch("FETT_JENKINS_PASSWORD")
|
6
|
+
|
7
|
+
def self.new_job(repository, template)
|
8
|
+
client.job.create(repository.jenkins_name, template.content)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.new_build(repository, params)
|
12
|
+
client.job.build(repository.jenkins_name, params)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def self.client
|
18
|
+
JenkinsApi::Client.new(server_url: URL, username: USERNAME, password: PASSWORD)
|
19
|
+
end
|
20
|
+
|
21
|
+
private_class_method :client
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Fett
|
2
|
+
class Listener
|
3
|
+
FETT_URL = ENV.fetch("FETT_URL")
|
4
|
+
|
5
|
+
def self.listen(fullname)
|
6
|
+
repository = RepositoryFactory.build(fullname)
|
7
|
+
|
8
|
+
template = Template.new("default", {
|
9
|
+
:name => "default",
|
10
|
+
:repo => "git@github.com:#{fullname}.git",
|
11
|
+
:callback_url => "#{FETT_URL}/builds/status"
|
12
|
+
})
|
13
|
+
|
14
|
+
Jenkins.new_job(repository, template)
|
15
|
+
GitHub.new_hook(repository)
|
16
|
+
Slack.listening_message(repository)
|
17
|
+
Repositories.put(repository)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "redis"
|
2
|
+
|
3
|
+
module Fett
|
4
|
+
class RedisConnection
|
5
|
+
HOST = ENV.fetch("FETT_REDIS_HOST")
|
6
|
+
PORT = ENV.fetch("FETT_REDIS_PORT")
|
7
|
+
PASSWORD = ENV.fetch("FETT_REDIS_PASSWORD")
|
8
|
+
DATABASE = ENV.fetch("FETT_REDIS_DATABASE")
|
9
|
+
|
10
|
+
def self.initialize
|
11
|
+
params = { host: HOST, port: PORT, db: DATABASE }
|
12
|
+
params.merge!(password: PASSWORD) unless PASSWORD.empty?
|
13
|
+
@@client ||= ::Redis.new(params)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.client
|
17
|
+
@@client
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Fett
|
2
|
+
class Repositories
|
3
|
+
def self.put(repository)
|
4
|
+
RedisConnection.client.set("repositories:#{repository.fullname}", Marshal.dump(repository))
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.repository_of_fullname(fullname)
|
8
|
+
Marshal.load(RedisConnection.client.get("repositories:#{fullname}"))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Fett
|
2
|
+
class Repository
|
3
|
+
attr_reader :last_pusher, :last_message, :last_commit_url, :last_branch
|
4
|
+
attr_writer :last_pusher, :last_message, :last_commit_url, :last_branch
|
5
|
+
|
6
|
+
def initialize(org, repository)
|
7
|
+
@org = org
|
8
|
+
@repository = repository
|
9
|
+
end
|
10
|
+
|
11
|
+
def jenkins_name
|
12
|
+
"#{@org}.#{@repository}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def fullname
|
16
|
+
"#{@org}/#{@repository}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(another_repository)
|
20
|
+
fullname == another_repository.fullname
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/fett/slack.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "json"
|
2
|
+
require "nestful"
|
3
|
+
|
4
|
+
module Fett
|
5
|
+
class Slack
|
6
|
+
SLACK_URL = ENV.fetch("FETT_SLACK_URL")
|
7
|
+
|
8
|
+
def self.listening_message(repository)
|
9
|
+
send_listening_message("kk, I'm listening to https://github.com/#{repository.fullname}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.build_status_message(repository, status)
|
13
|
+
send_build_status_message(BuildStatusMessageComposer.compose(repository, status), status)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def self.send_listening_message(message)
|
19
|
+
send_message(text: message)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.send_build_status_message(message, status)
|
23
|
+
color = BuildStatusColorHandler.color(status)
|
24
|
+
|
25
|
+
attachment = {
|
26
|
+
color: color,
|
27
|
+
fields: [ value: message, short: true ]
|
28
|
+
}
|
29
|
+
|
30
|
+
send_message(attachments: [attachment])
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.send_message(params)
|
34
|
+
params.merge!(username: "Fett", icon_url: "http://imagizer.imageshack.us/v2/150x100q90/538/K7OlPJ.png")
|
35
|
+
Nestful.post(SLACK_URL, payload: JSON.dump(params))
|
36
|
+
end
|
37
|
+
|
38
|
+
private_class_method :send_listening_message, :send_build_status_message, :send_message
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "tilt"
|
2
|
+
|
3
|
+
module Fett
|
4
|
+
class Template
|
5
|
+
def initialize(template_name, params = {})
|
6
|
+
@name = template_name
|
7
|
+
@params = params
|
8
|
+
end
|
9
|
+
|
10
|
+
def content
|
11
|
+
parse_template(@name, @params)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def parse_template(template_name, params)
|
17
|
+
template_name ||= "rb"
|
18
|
+
|
19
|
+
template = Tilt.new(path_for_template(template_name))
|
20
|
+
|
21
|
+
template.render(Object.new, params)
|
22
|
+
end
|
23
|
+
|
24
|
+
def path_for_template(template_name)
|
25
|
+
File.expand_path("../../../templates/#{template_name}.xml.erb", __FILE__)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "fett/build_status_color_handler"
|
2
|
+
|
3
|
+
describe Fett::BuildStatusColorHandler do
|
4
|
+
it "returns a color based on the status" do
|
5
|
+
expect(Fett::BuildStatusColorHandler.color("success")).to eq("#36a64f")
|
6
|
+
expect(Fett::BuildStatusColorHandler.color("failure")).to eq("#36a64f")
|
7
|
+
expect(Fett::BuildStatusColorHandler.color("pending")).to eq("#36a64f")
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "fett/build_status_message_composer"
|
2
|
+
|
3
|
+
describe Fett::BuildStatusMessageComposer do
|
4
|
+
let(:repository) do
|
5
|
+
double(:repository,
|
6
|
+
last_pusher: "pedrogimenez",
|
7
|
+
last_message: "Testing fett.",
|
8
|
+
last_commit_url: "https://github.com/pedrogimenez/fett/commit/7240a7b561e56702889565e46d6ba93784fe23c5",
|
9
|
+
last_branch: "master",
|
10
|
+
fullname: "pedrogimenez/fett")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "composes a message when the status is `pending`" do
|
14
|
+
message = Fett::BuildStatusMessageComposer.compose(repository, "pending")
|
15
|
+
|
16
|
+
expect(message).to eq("Build of pedrogimenez/fett@<#{repository.last_commit_url}|master> by pedrogimenez.")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "composes a message when the status is `success`" do
|
20
|
+
message = Fett::BuildStatusMessageComposer.compose(repository, "success")
|
21
|
+
|
22
|
+
expect(message).to eq("Build of pedrogimenez/fett@<#{repository.last_commit_url}|master> by pedrogimenez. Well done :sparkles:.")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "composes a message when the status is `failure`" do
|
26
|
+
message = Fett::BuildStatusMessageComposer.compose(repository, "failure")
|
27
|
+
|
28
|
+
expect(message).to eq("Build of pedrogimenez/fett@<#{repository.last_commit_url}|master> by pedrogimenez.")
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "fett/build_status_notifier"
|
2
|
+
|
3
|
+
module Fett
|
4
|
+
class Repositories; end
|
5
|
+
class GitHub; end
|
6
|
+
class Slack; end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Fett::BuildStatusNotifier do
|
10
|
+
it "notifies GitHub and Slack about the last build status" do
|
11
|
+
repository = double(:repository)
|
12
|
+
expect(Fett::Repositories).to receive(:repository_of_fullname).and_return(repository)
|
13
|
+
expect(Fett::GitHub).to receive(:new_build_status)
|
14
|
+
.with(repository, "7240a7b561e56702889565e46d6ba93784fe23c5", "success")
|
15
|
+
expect(Fett::Slack).to receive(:build_status_message)
|
16
|
+
.with(repository, "success")
|
17
|
+
|
18
|
+
Fett::BuildStatusNotifier.notify("pedrogimenez.fett", "7240a7b561e56702889565e46d6ba93784fe23c5", "success")
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "fett/builder"
|
2
|
+
|
3
|
+
module Fett
|
4
|
+
class Repositories; end
|
5
|
+
class Jenkins; end
|
6
|
+
end
|
7
|
+
|
8
|
+
describe Fett::Builder do
|
9
|
+
it "builds a commit and returns the result" do
|
10
|
+
repository = double(:repository, :last_pusher= => nil, :last_message= => nil, :last_commit_url= => nil, :last_branch= => nil)
|
11
|
+
|
12
|
+
expect(Fett::Repositories).to receive(:repository_of_fullname).with("pedrogimenez/fett").and_return(repository)
|
13
|
+
expect(Fett::Jenkins).to receive(:new_build).with(repository, params)
|
14
|
+
expect(Fett::Repositories).to receive(:put).with(repository)
|
15
|
+
|
16
|
+
Fett::Builder.build("pedrogimenez/fett", "pedrogimenez", "Testing fett.",
|
17
|
+
"https://github.com/pedrogimenez/fett/commit/7240a7b561e56702889565e46d6ba93784fe23c5", "refs/heads/master", params)
|
18
|
+
end
|
19
|
+
|
20
|
+
def params
|
21
|
+
{ GIT_SHA1: "7240a7b561e56702889565e46d6ba93784fe23c5", GIT_BRANCH: "master" }
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
ENV["FETT_GITHUB_LOGIN"] = "pedrogimenez"
|
2
|
+
ENV["FETT_GITHUB_PASSWORD"] = "pedrogimenez"
|
3
|
+
ENV["FETT_URL"] = "https://fett.pedrogimenez.com"
|
4
|
+
|
5
|
+
require "fett/github"
|
6
|
+
|
7
|
+
module Fett
|
8
|
+
class GitHubStatusMessageComposer; end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Octokit
|
12
|
+
class Client; end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Fett::GitHub do
|
16
|
+
it "creates a new hook in GitHub" do
|
17
|
+
repository = double(:repository, fullname: "pedrogimenez/fett")
|
18
|
+
client = double(:client)
|
19
|
+
|
20
|
+
expect(Octokit::Client).to receive(:new).and_return(client)
|
21
|
+
expect(client).to receive(:create_hook).with("pedrogimenez/fett", "web", *params)
|
22
|
+
|
23
|
+
Fett::GitHub.new_hook(repository)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "sets the last build status" do
|
27
|
+
repository = double(:repository, fullname: "pedrogimenez/fett")
|
28
|
+
client = double(:client)
|
29
|
+
|
30
|
+
expect(Fett::GitHubStatusMessageComposer).to receive(:compose).with("success").and_return("yay!")
|
31
|
+
expect(Octokit::Client).to receive(:new).and_return(client)
|
32
|
+
expect(client).to receive(:create_status)
|
33
|
+
.with("pedrogimenez/fett", "7240a7b561e56702889565e46d6ba93784fe23c5", "success", { description: "yay!" })
|
34
|
+
|
35
|
+
Fett::GitHub.new_build_status(repository, "7240a7b561e56702889565e46d6ba93784fe23c5", "success")
|
36
|
+
end
|
37
|
+
|
38
|
+
def params
|
39
|
+
[{
|
40
|
+
url: "https://fett.pedrogimenez.com/builds",
|
41
|
+
content_type: "json"
|
42
|
+
},
|
43
|
+
{
|
44
|
+
events: ["push"],
|
45
|
+
active: true
|
46
|
+
}]
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "fett/github_status_message_composer"
|
2
|
+
|
3
|
+
describe Fett::GitHubStatusMessageComposer do
|
4
|
+
it "composes a messages based on the given status" do
|
5
|
+
expect(Fett::GitHubStatusMessageComposer.compose("success")).
|
6
|
+
to eq("I love this code. I love it more than sharks love blood")
|
7
|
+
expect(Fett::GitHubStatusMessageComposer.compose("failure")).
|
8
|
+
to eq("My great concern is not whether you have failed, but whether you are content with your failure")
|
9
|
+
expect(Fett::GitHubStatusMessageComposer.compose("pending")).
|
10
|
+
to eq("Don’t count every hour in the day, make every hour in the day count")
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
ENV["FETT_JENKINS_URL"] = "https://jenkins.pedrogimenez.com"
|
2
|
+
ENV["FETT_JENKINS_USERNAME"] = "pedrogimenez"
|
3
|
+
ENV["FETT_JENKINS_PASSWORD"] = "pedrogimenez"
|
4
|
+
|
5
|
+
require "fett/jenkins"
|
6
|
+
|
7
|
+
module JenkinsApi
|
8
|
+
class Client; end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe Fett::Jenkins do
|
12
|
+
it "creates a new job in Jenkins" do
|
13
|
+
client = double(:client)
|
14
|
+
repository = double(:repository, jenkins_name: "pedrogimenez.fett")
|
15
|
+
template = double(:template, content: "template")
|
16
|
+
|
17
|
+
expect(JenkinsApi::Client).to receive(:new).and_return(client)
|
18
|
+
expect(client).to receive(:job).and_return(client)
|
19
|
+
expect(client).to receive(:create).with("pedrogimenez.fett", "template")
|
20
|
+
|
21
|
+
Fett::Jenkins.new_job(repository, template)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "runs a new build in Jenkins" do
|
25
|
+
client = double(:client)
|
26
|
+
repository = double(:repository, jenkins_name: "pedrogimenez.fett")
|
27
|
+
|
28
|
+
expect(JenkinsApi::Client).to receive(:new).and_return(client)
|
29
|
+
expect(client).to receive(:job).and_return(client)
|
30
|
+
expect(client).to receive(:build).with("pedrogimenez.fett", params)
|
31
|
+
|
32
|
+
Fett::Jenkins.new_build(repository, params)
|
33
|
+
end
|
34
|
+
|
35
|
+
def params
|
36
|
+
{ GIT_SHA1: "7240a7b561e56702889565e46d6ba93784fe23c5", GIT_BRANCH: "master" }
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "fett/listener"
|
2
|
+
|
3
|
+
module Fett
|
4
|
+
class RepositoryFactory; end
|
5
|
+
class Jenkins; end
|
6
|
+
class GitHub; end
|
7
|
+
class Slack; end
|
8
|
+
class Repositories; end
|
9
|
+
class Template; end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Fett::Listener do
|
13
|
+
it "listens new repositories" do
|
14
|
+
repository = double(:repository)
|
15
|
+
template = double(:template)
|
16
|
+
expect(Fett::RepositoryFactory).to receive(:build).with("pedrogimenez/fett").and_return(repository)
|
17
|
+
expect(Fett::Template).to receive(:new).and_return(template)
|
18
|
+
expect(Fett::Jenkins).to receive(:new_job).with(repository, template)
|
19
|
+
expect(Fett::GitHub).to receive(:new_hook).with(repository)
|
20
|
+
expect(Fett::Slack).to receive(:listening_message).with(repository)
|
21
|
+
expect(Fett::Repositories).to receive(:put).with(repository)
|
22
|
+
|
23
|
+
Fett::Listener.listen("pedrogimenez/fett")
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "fett/repository"
|
2
|
+
require "fett/repository_factory"
|
3
|
+
require "fett/repositories"
|
4
|
+
require "fett/redis_connection"
|
5
|
+
|
6
|
+
describe Fett::Repositories do
|
7
|
+
before do
|
8
|
+
Fett::RedisConnection.initialize
|
9
|
+
Fett::RedisConnection.client.flushdb
|
10
|
+
end
|
11
|
+
|
12
|
+
it "persists a repository" do
|
13
|
+
client = double(:client)
|
14
|
+
repository = Fett::RepositoryFactory.build("pedrogimenez/fett")
|
15
|
+
|
16
|
+
expect(Fett::RedisConnection).to receive(:client).and_return(client)
|
17
|
+
expect(client).to receive(:set)
|
18
|
+
|
19
|
+
Fett::Repositories.put(repository)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "updates a repository" do
|
23
|
+
new_repository = Fett::RepositoryFactory.build("pedrogimenez/fett")
|
24
|
+
Fett::Repositories.put(new_repository)
|
25
|
+
|
26
|
+
new_repository.last_pusher = "pedrogimenez"
|
27
|
+
Fett::Repositories.put(new_repository)
|
28
|
+
|
29
|
+
repository = Fett::Repositories.repository_of_fullname("pedrogimenez/fett")
|
30
|
+
expect(repository.last_pusher).to eq("pedrogimenez")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns a repository of fullname" do
|
34
|
+
repository = Fett::RepositoryFactory.build("pedrogimenez/fett")
|
35
|
+
Fett::Repositories.put(repository)
|
36
|
+
|
37
|
+
expect(Fett::Repositories.repository_of_fullname("pedrogimenez/fett")).to eq(repository)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
ENV["FETT_SLACK_URL"] = "https://slack.com"
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "fett/slack"
|
5
|
+
|
6
|
+
module Nestful
|
7
|
+
class Request; end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Fett
|
11
|
+
class BuildStatusMessageComposer; end
|
12
|
+
class BuildStatusColorHandler; end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Fett::Slack do
|
16
|
+
it "sends a message to Slack when a repository is added to the system" do
|
17
|
+
repository = double(:repository, fullname: "pedrogimenez/fett")
|
18
|
+
expect(Nestful).to receive(:post).with("https://slack.com", listening_message_params)
|
19
|
+
|
20
|
+
Fett::Slack.listening_message(repository)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "sends a message to Slack about the last build status" do
|
24
|
+
repository = double(:repository)
|
25
|
+
|
26
|
+
expect(Fett::BuildStatusMessageComposer).to receive(:compose).with(repository, "success").and_return("yay!")
|
27
|
+
expect(Fett::BuildStatusColorHandler).to receive(:color).with("success").and_return("#fff000")
|
28
|
+
expect(Nestful).to receive(:post).with("https://slack.com", build_message_params)
|
29
|
+
|
30
|
+
Fett::Slack.build_status_message(repository, "success")
|
31
|
+
end
|
32
|
+
|
33
|
+
def listening_message_params
|
34
|
+
{
|
35
|
+
payload: JSON.dump(
|
36
|
+
text: "kk, I'm listening to https://github.com/pedrogimenez/fett",
|
37
|
+
username: "Fett",
|
38
|
+
icon_url: "http://imagizer.imageshack.us/v2/150x100q90/538/K7OlPJ.png")
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_message_params
|
43
|
+
{
|
44
|
+
payload: JSON.dump(
|
45
|
+
attachments: [
|
46
|
+
color: "#fff000",
|
47
|
+
fields: [ value: "yay!", short: true ]
|
48
|
+
],
|
49
|
+
username: "Fett",
|
50
|
+
icon_url: "http://imagizer.imageshack.us/v2/150x100q90/538/K7OlPJ.png")
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
2
|
+
<project>
|
3
|
+
<actions/>
|
4
|
+
<name><%= name %></name>
|
5
|
+
<logRotator>
|
6
|
+
<daysToKeep>-1</daysToKeep>
|
7
|
+
<numToKeep>-1</numToKeep>
|
8
|
+
<artifactDaysToKeep>-1</artifactDaysToKeep>
|
9
|
+
<artifactNumToKeep>-1</artifactNumToKeep>
|
10
|
+
</logRotator>
|
11
|
+
<keepDependencies>false</keepDependencies>
|
12
|
+
<properties>
|
13
|
+
<hudson.model.ParametersDefinitionProperty>
|
14
|
+
<parameterDefinitions>
|
15
|
+
<hudson.model.StringParameterDefinition>
|
16
|
+
<name>GIT_SHA1</name>
|
17
|
+
<description></description>
|
18
|
+
<defaultValue></defaultValue>
|
19
|
+
</hudson.model.StringParameterDefinition>
|
20
|
+
<hudson.model.StringParameterDefinition>
|
21
|
+
<name>GIT_BRANCH</name>
|
22
|
+
<description></description>
|
23
|
+
<defaultValue></defaultValue>
|
24
|
+
</hudson.model.StringParameterDefinition>
|
25
|
+
</parameterDefinitions>
|
26
|
+
</hudson.model.ParametersDefinitionProperty>
|
27
|
+
<com.tikal.hudson.plugins.notification.HudsonNotificationProperty>
|
28
|
+
<endpoints>
|
29
|
+
<com.tikal.hudson.plugins.notification.Endpoint>
|
30
|
+
<protocol>HTTP</protocol>
|
31
|
+
<url><%= callback_url %></url>
|
32
|
+
</com.tikal.hudson.plugins.notification.Endpoint>
|
33
|
+
</endpoints>
|
34
|
+
</com.tikal.hudson.plugins.notification.HudsonNotificationProperty>
|
35
|
+
</properties>
|
36
|
+
<scm class="hudson.scm.NullSCM"/>
|
37
|
+
<assignedNode>master</assignedNode>
|
38
|
+
<canRoam>false</canRoam>
|
39
|
+
<disabled>false</disabled>
|
40
|
+
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
|
41
|
+
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
|
42
|
+
<triggers class="vector"/>
|
43
|
+
<concurrentBuild>false</concurrentBuild>
|
44
|
+
<builders>
|
45
|
+
<hudson.tasks.Shell>
|
46
|
+
<command>
|
47
|
+
if [ ! -d "./.git" ]; then
|
48
|
+
git init
|
49
|
+
git remote add origin <%= repo %>
|
50
|
+
fi
|
51
|
+
git fetch -q origin
|
52
|
+
git reset -q --hard $GIT_SHA1
|
53
|
+
bundle install --path vendor/gems --binstubs
|
54
|
+
script/cibuild
|
55
|
+
</command>
|
56
|
+
</hudson.tasks.Shell>
|
57
|
+
</builders>
|
58
|
+
<publishers/>
|
59
|
+
<buildWrappers/>
|
60
|
+
</project>
|
metadata
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fett
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pedro Gimenez
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redis
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sinatra
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sinatra-contrib
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: nestful
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: octokit
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: jenkins_api_client
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: tilt
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.4.1
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.4.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec-mocks
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 3.0.2
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 3.0.2
|
139
|
+
description: Fett brings all the information about your builds right to your chat
|
140
|
+
room
|
141
|
+
email:
|
142
|
+
- me@pedro.bz
|
143
|
+
executables: []
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files:
|
146
|
+
- README.md
|
147
|
+
files:
|
148
|
+
- README.md
|
149
|
+
- lib/fett.rb
|
150
|
+
- lib/fett/app.rb
|
151
|
+
- lib/fett/build_status_color_handler.rb
|
152
|
+
- lib/fett/build_status_message_composer.rb
|
153
|
+
- lib/fett/build_status_notifier.rb
|
154
|
+
- lib/fett/builder.rb
|
155
|
+
- lib/fett/github.rb
|
156
|
+
- lib/fett/github_status_message_composer.rb
|
157
|
+
- lib/fett/jenkins.rb
|
158
|
+
- lib/fett/listener.rb
|
159
|
+
- lib/fett/redis_connection.rb
|
160
|
+
- lib/fett/repositories.rb
|
161
|
+
- lib/fett/repository.rb
|
162
|
+
- lib/fett/repository_factory.rb
|
163
|
+
- lib/fett/slack.rb
|
164
|
+
- lib/fett/template.rb
|
165
|
+
- spec/fett/build_status_color_handler.rb
|
166
|
+
- spec/fett/build_status_message_composer_spec.rb
|
167
|
+
- spec/fett/build_status_notifier_spec.rb
|
168
|
+
- spec/fett/builder_spec.rb
|
169
|
+
- spec/fett/github_spec.rb
|
170
|
+
- spec/fett/github_status_message_composer_spec.rb
|
171
|
+
- spec/fett/jenkins_spec.rb
|
172
|
+
- spec/fett/listener_spec.rb
|
173
|
+
- spec/fett/repositories_spec.rb
|
174
|
+
- spec/fett/slack_spec.rb
|
175
|
+
- templates/default.xml.erb
|
176
|
+
homepage: https://github.com/pedrogimenez/fett
|
177
|
+
licenses:
|
178
|
+
- MIT
|
179
|
+
metadata: {}
|
180
|
+
post_install_message:
|
181
|
+
rdoc_options: []
|
182
|
+
require_paths:
|
183
|
+
- lib
|
184
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: 2.1.0
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
requirements: []
|
195
|
+
rubyforge_project:
|
196
|
+
rubygems_version: 2.2.0
|
197
|
+
signing_key:
|
198
|
+
specification_version: 4
|
199
|
+
summary: Fett brings all the information about your builds right to your chat room
|
200
|
+
test_files: []
|