fett 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|