build_status_server 0.4

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem "json"
6
+
7
+ group :test do
8
+ gem "rspec"
9
+ end
10
+
11
+ group :development do
12
+ gem "ruby-debug"
13
+ gem "sinatra"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,46 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ build_status_server (0.3)
5
+ json
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ columnize (0.3.6)
11
+ diff-lcs (1.1.3)
12
+ json (1.6.5)
13
+ linecache (0.46)
14
+ rbx-require-relative (> 0.0.4)
15
+ rack (1.4.0)
16
+ rack-protection (1.2.0)
17
+ rack
18
+ rbx-require-relative (0.0.5)
19
+ rspec (2.9.0)
20
+ rspec-core (~> 2.9.0)
21
+ rspec-expectations (~> 2.9.0)
22
+ rspec-mocks (~> 2.9.0)
23
+ rspec-core (2.9.0)
24
+ rspec-expectations (2.9.0)
25
+ diff-lcs (~> 1.1.3)
26
+ rspec-mocks (2.9.0)
27
+ ruby-debug (0.10.4)
28
+ columnize (>= 0.1)
29
+ ruby-debug-base (~> 0.10.4.0)
30
+ ruby-debug-base (0.10.4)
31
+ linecache (>= 0.3)
32
+ sinatra (1.3.2)
33
+ rack (~> 1.3, >= 1.3.6)
34
+ rack-protection (~> 1.2)
35
+ tilt (~> 1.3, >= 1.3.3)
36
+ tilt (1.3.3)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ build_status_server!
43
+ json
44
+ rspec
45
+ ruby-debug
46
+ sinatra
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Juan C. Muller <jcmuller@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Build Notifier
2
+
3
+ This utility is part of an XFD (eXtreeme Feedback Device) solution designed and
4
+ built for my employer [ChallengePost](http://challengepost.com). It works in
5
+ conjunction with our [Jenkins](http://jenkins-ci.org) Continuous Integration
6
+ server (and its
7
+ [Notification Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Notification+Plugin))
8
+ and an [Arduino](http://arduino.cc) powered
9
+ [Traffic Light controller](https://github.com/jcmuller/TrafficLightController)
10
+ with a
11
+ pseudo-[REST](http://en.wikipedia.org/wiki/Representational_state_transfer)ful
12
+ API.
13
+
14
+ To run, you need to copy `config/config-example.yml` into `config/config.yml`
15
+ and mofify accordingly.
16
+
17
+ # Configuration file
18
+ ## UDP Server
19
+ This section defines what interface and port should the UDP server listen at.
20
+ The Jenkins' Notification Plugin should be set to this parameters as well.
21
+
22
+ ## TCP Client
23
+ This section is where we tell the server how to communicate with the web
24
+ enabled XFD. In the example case, there is a web server running somewhere
25
+ listening on port 4567 that responds to `/green` and `/red`.
26
+
27
+ On our installation, this represents the Traffic Light's Arduino web server.
28
+
29
+ ## Store
30
+ Where the persistent state will be stored.
31
+
32
+ ## Mask (optional)
33
+ You can decide to either include or ignore certain builds whose names match a
34
+ given [Regular Expression](http://en.wikipedia.org/wiki/Regular_expression).
35
+
36
+ # Development
37
+
38
+ `bin/build_status_server_traffic_light_mock` is provided for development
39
+ purposes only.
40
+
41
+ # Finished product
42
+ ![my image](http://i.imgur.com/aK5rs.jpg)
43
+
44
+ # Wiring the traffic light
45
+ ![my image](http://i.imgur.com/gUpWe.jpg)
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'getoptlong'
4
+
5
+ possible_arguments = [
6
+ ['--config', '-c', GetoptLong::OPTIONAL_ARGUMENT],
7
+ ['--development', '-d', GetoptLong::NO_ARGUMENT],
8
+ ['--help', '-h', GetoptLong::NO_ARGUMENT],
9
+ ['--verbose', '-v', GetoptLong::NO_ARGUMENT]
10
+ ]
11
+
12
+ opts = GetoptLong.new(*possible_arguments)
13
+
14
+ options = {}
15
+ showhelp = false
16
+
17
+ opts.each do |opt, arg|
18
+ case opt
19
+ when '--development'
20
+ $:.push 'lib'
21
+ when '--help'
22
+ puts <<-EOT
23
+ #{File.basename(__FILE__)} [#{possible_arguments.map{|arg| arg[0] + (arg[2] == GetoptLong::OPTIONAL_ARGUMENT ? ' argument' : '')}.join('], [')}]
24
+
25
+ All the arguments are optional.
26
+
27
+ --config, -c Specify what configuration file to load
28
+ --development, -d Whether we should load libraries from ./lib
29
+ --help, -h Display this very helpful text
30
+ --verbose, -v Be more informative about what's going on
31
+
32
+ EOT
33
+ exit
34
+ when '--config'
35
+ options[:config] = arg
36
+ when '--verbose'
37
+ options[:verbose] = true
38
+ end
39
+ end
40
+
41
+ require 'build_status_server'
42
+
43
+ BuildStatusServer::Server.new(options).listen
@@ -0,0 +1,29 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "build_status_server/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "build_status_server"
6
+ s.version = BuildStatusServer::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+
9
+ s.author = "Juan C. Muller"
10
+ s.email = "jcmuller@gmail.com"
11
+ s.homepage = "http://github.com/jcmuller/build_status_server"
12
+ s.license = "GPL"
13
+ s.summary = "A build notifier server for Jenkins CI that controls an XFD over HTTP"
14
+ s.description = "A build notifier server for Jenkins CI that controls an XFD over HTTP"
15
+
16
+ s.files = Dir["{lib/**/*,spec/**/*}"] + %w(bin/build_status_server config/config-example.yml LICENSE README.md Gemfile Gemfile.lock build_status_server.gemspec)
17
+ s.require_path = "lib"
18
+ s.bindir = "bin"
19
+ s.executables = %w(build_status_server)
20
+
21
+ s.homepage = "http://github.com/jcmuller/build_status_server"
22
+ s.test_files = Dir["spec/**/*_spec.rb"]
23
+
24
+ s.add_development_dependency("ruby-debug")
25
+ s.add_development_dependency("sinatra")
26
+
27
+ s.add_dependency("json")
28
+ end
29
+
@@ -0,0 +1,14 @@
1
+ udp_server:
2
+ address: '127.0.0.1'
3
+ port: 1234
4
+ tcp_client:
5
+ host: '127.0.0.1'
6
+ port: 4567
7
+ pass: "/green"
8
+ fail: "/red"
9
+ store:
10
+ filename: "/tmp/build_result.yml"
11
+ mask:
12
+ regex: ".*master.*"
13
+ policy: "include"
14
+ verbose: true
@@ -0,0 +1,8 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ Bundler.require(:default)
5
+
6
+ require "socket"
7
+ require "timeout"
8
+ require "yaml"
@@ -0,0 +1,199 @@
1
+ module BuildStatusServer
2
+ class Server
3
+ attr_reader :config, :store_file, :mask_policy, :verbose
4
+ attr_accessor :store, :mask
5
+
6
+ def initialize(options = {})
7
+ load_config_file(options[:config])
8
+ @verbose = options[:verbose] || config["verbose"]
9
+ @store_file = File.expand_path(".", config["store"]["filename"])
10
+ @mask = Regexp.new(config["mask"]["regex"])
11
+ @mask_policy = config["mask"]["policy"] || "exclude"
12
+ end
13
+
14
+ def load_store
15
+ @store = begin
16
+ YAML.load_file(store_file)
17
+ rescue
18
+ {}
19
+ end
20
+ @store = {} unless store.class == Hash
21
+ end
22
+
23
+ def listen
24
+ sock = UDPSocket.new
25
+ udp_server = config["udp_server"]
26
+
27
+ begin
28
+ sock.bind(udp_server["address"], udp_server["port"])
29
+ rescue Errno::EADDRINUSE
30
+ STDERR.puts <<-EOT
31
+ There appears that another instance is running, or another process
32
+ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
33
+
34
+ EOT
35
+ exit
36
+ end
37
+
38
+ puts "Listening on UDP #{udp_server["address"]}:#{udp_server["port"]}" if verbose
39
+
40
+ while true
41
+ data, addr = sock.recvfrom(2048)
42
+ #require "ruby-debug"; debugger
43
+ if process_job(data)
44
+ status = process_all_statuses
45
+ notify(status)
46
+ end
47
+ end
48
+
49
+ sock.close
50
+ end
51
+
52
+ def process_job(data = "{}")
53
+ job = JSON.parse(data)
54
+
55
+ build_name = job["name"]
56
+
57
+ unless should_process_build(build_name)
58
+ STDOUT.puts "Ignoring #{build_name} (#{mask}--#{mask_policy})" if verbose
59
+ return false
60
+ end
61
+
62
+ if job.class != Hash or
63
+ job["build"].class != Hash
64
+ STDERR.puts "Pinged with an invalid payload"
65
+ return false
66
+ end
67
+
68
+ phase = job["build"]["phase"]
69
+ status = job["build"]["status"]
70
+
71
+ if phase == "FINISHED"
72
+ STDOUT.puts "Got #{status} for #{build_name} on #{Time.now} [#{job.inspect}]" if verbose
73
+ case status
74
+ when "SUCCESS", "FAILURE"
75
+ load_store
76
+ store[build_name] = status
77
+ File.open(store_file, "w") { |file| YAML.dump(store, file) }
78
+ return true
79
+ end
80
+ else
81
+ STDOUT.puts "Started for #{build_name} on #{Time.now} [#{job.inspect}]" if verbose
82
+ end
83
+
84
+ return false
85
+ end
86
+
87
+ # Ensure config file exists. If not, copy example into it
88
+ def load_config_file(config_file)
89
+ curated_file = nil
90
+
91
+ if config_file
92
+ f = File.expand_path(config_file)
93
+ if File.exists?(f)
94
+ curated_file = f
95
+ else
96
+ puts "Supplied config file (#{config_file}) doesn't seem to exist" if verbose
97
+ exit
98
+ end
99
+ else
100
+ locations_to_try = %w(
101
+ ~/.config/build_status_server/config.yml
102
+ config/config.yml
103
+ /etc/build_status_server/config.yml
104
+ /usr/local/etc/build_status_server/config.yml
105
+ )
106
+
107
+ locations_to_try.each do |possible_conf_file|
108
+ f = File.expand_path(possible_conf_file)
109
+ if File.exists?(f)
110
+ puts "Using #{possible_conf_file}!" if verbose if verbose
111
+ curated_file = f
112
+ break
113
+ end
114
+ end
115
+
116
+ puts <<-EOT
117
+ Looks like there isn't an available configuration file for this program. You
118
+ can create one in any of the following locations:
119
+
120
+ #{locations_to_try.map{|l| File.expand_path(l)}.join("\n ")}
121
+
122
+ Here is a sample of the contents for that file:
123
+
124
+ #{File.open("#{File.dirname(File.expand_path(__FILE__))}/../../config/config-example.yml").read}
125
+
126
+ EOT
127
+
128
+ exit
129
+ end
130
+
131
+ puts "Using #{curated_file}!" if verbose
132
+ @config = YAML.load_file(curated_file)
133
+ end
134
+
135
+ def should_process_build(build_name)
136
+ # If mask exists, then ...
137
+ ! (!!mask && ((mask_policy == "include" && build_name !~ mask) ||
138
+ (mask_policy != "include" && build_name =~ mask)))
139
+ end
140
+
141
+ def process_all_statuses
142
+ pass = true
143
+
144
+ @store.values.each do |val|
145
+ pass &&= (val == "pass" || val == "SUCCESS")
146
+ end
147
+
148
+ pass
149
+ end
150
+
151
+ def notify(status)
152
+ tcp_client = config["tcp_client"]
153
+
154
+ attempts = 0
155
+ light = status ? tcp_client["pass"] : tcp_client["fail"]
156
+
157
+ begin
158
+ timeout(5) do
159
+ attempts += 1
160
+ client = TCPSocket.new(tcp_client["host"], tcp_client["port"])
161
+ client.print "GET #{light} HTTP/1.0\n\n"
162
+ answer = client.gets(nil)
163
+ STDOUT.puts answer if verbose
164
+ client.close
165
+ end
166
+ rescue Timeout::Error => ex
167
+ STDERR.puts "Error: #{ex} while trying to send #{light}"
168
+ retry unless attempts > 2
169
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => ex
170
+ STDERR.puts "Error: #{ex} while trying to send #{light}"
171
+ STDERR.puts "Will wait for 2 seconds and try again..."
172
+ sleep 2
173
+ retry unless attempts > 2
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ __END__
180
+
181
+ Example payload:
182
+ {
183
+ "name":"test",
184
+ "url":"job/test/",
185
+ "build":{
186
+ "full_url":"http://cronus.local:3001/job/test/20/",
187
+ "number":20,
188
+ "phase":"FINISHED",
189
+ "status":"SUCCESS",
190
+ "url":"job/test/20/"
191
+ }
192
+ }
193
+
194
+ We're getting this error once in a while:
195
+ /usr/local/lib/ruby/1.8/timeout.rb:64:in `notify': execution expired (Timeout::Error)
196
+ from /home/jcmuller/build_notifier/lib/server.rb:102:in `notify'
197
+ from /home/jcmuller/build_notifier/lib/server.rb:33:in `listen'
198
+ from bin/server:5
199
+
@@ -0,0 +1,189 @@
1
+ module BuildStatusServer
2
+ class Server
3
+ attr_reader :config, :store_file, :mask_policy, :verbose
4
+ attr_accessor :store, :mask
5
+
6
+ def initialize(options = {})
7
+ load_config_file(options[:config])
8
+ @verbose = options[:verbose] || config["verbose"]
9
+ @store_file = File.expand_path(".", config["store"]["filename"])
10
+ @mask = Regexp.new(config["mask"]["regex"])
11
+ @mask_policy = config["mask"]["policy"] || "exclude"
12
+ end
13
+
14
+ def load_store
15
+ @store = begin
16
+ YAML.load_file(store_file)
17
+ rescue
18
+ {}
19
+ end
20
+ @store = {} unless store.class == Hash
21
+ end
22
+
23
+ def listen
24
+ sock = UDPSocket.new
25
+ udp_server = config["udp_server"]
26
+ sock.bind(udp_server["address"], udp_server["port"])
27
+
28
+ puts "Listening on UDP #{udp_server["address"]}:#{udp_server["port"]}" if verbose
29
+
30
+ while true
31
+ data, addr = sock.recvfrom(2048)
32
+ #require "ruby-debug"; debugger
33
+ if process_job(data)
34
+ status = process_all_statuses
35
+ notify(status)
36
+ end
37
+ end
38
+
39
+ sock.close
40
+ end
41
+
42
+ def process_job(data = "{}")
43
+ job = JSON.parse(data)
44
+
45
+ build_name = job["name"]
46
+
47
+ unless should_process_build(build_name)
48
+ STDOUT.puts "Ignoring #{build_name} (#{mask}--#{mask_policy})" if verbose
49
+ return false
50
+ end
51
+
52
+ if job.class != Hash or
53
+ job["build"].class != Hash
54
+ STDERR.puts "Pinged with an invalid payload"
55
+ return false
56
+ end
57
+
58
+ phase = job["build"]["phase"]
59
+ status = job["build"]["status"]
60
+
61
+ if phase == "FINISHED"
62
+ STDOUT.puts "Got #{status} for #{build_name} on #{Time.now} [#{job.inspect}]" if verbose
63
+ case status
64
+ when "SUCCESS", "FAILURE"
65
+ load_store
66
+ store[build_name] = status
67
+ File.open(store_file, "w") { |file| YAML.dump(store, file) }
68
+ return true
69
+ end
70
+ else
71
+ STDOUT.puts "Started for #{build_name} on #{Time.now} [#{job.inspect}]" if verbose
72
+ end
73
+
74
+ return false
75
+ end
76
+
77
+ # Ensure config file exists. If not, copy example into it
78
+ def load_config_file(config_file)
79
+ curated_file = nil
80
+
81
+ if config_file
82
+ f = File.expand_path(config_file)
83
+ if File.exists?(f)
84
+ curated_file = f
85
+ else
86
+ puts "Supplied config file (#{config_file}) doesn't seem to exist" if verbose
87
+ exit
88
+ end
89
+ else
90
+ locations_to_try = %w(
91
+ ~/.config/build_status_server/config.yml
92
+ config/config.yml
93
+ /etc/build_status_server/config.yml
94
+ /usr/local/etc/build_status_server/config.yml
95
+ )
96
+
97
+ locations_to_try.each do |possible_conf_file|
98
+ f = File.expand_path(possible_conf_file)
99
+ if File.exists?(f)
100
+ puts "Using #{possible_conf_file}!" if verbose if verbose
101
+ curated_file = f
102
+ break
103
+ end
104
+ end
105
+
106
+ puts <<-EOT
107
+ Looks like there isn't an available configuration file for this program. You
108
+ can create one in any of the following locations:
109
+
110
+ #{locations_to_try.map{|l| File.expand_path(l)}.join("\n ")}
111
+
112
+ Here is a sample of the contents for that file:
113
+
114
+ #{File.open("#{File.dirname(File.expand_path(__FILE__))}/../../config/config-example.yml").read}
115
+
116
+ EOT
117
+
118
+ exit
119
+ end
120
+
121
+ puts "Using #{curated_file}!" if verbose
122
+ @config = YAML.load_file(curated_file)
123
+ end
124
+
125
+ def should_process_build(build_name)
126
+ # If mask exists, then ...
127
+ ! (!!mask && ((mask_policy == "include" && build_name !~ mask) ||
128
+ (mask_policy != "include" && build_name =~ mask)))
129
+ end
130
+
131
+ def process_all_statuses
132
+ pass = true
133
+
134
+ @store.values.each do |val|
135
+ pass &&= (val == "pass" || val == "SUCCESS")
136
+ end
137
+
138
+ pass
139
+ end
140
+
141
+ def notify(status)
142
+ tcp_client = config["tcp_client"]
143
+
144
+ attempts = 0
145
+ light = status ? tcp_client["pass"] : tcp_client["fail"]
146
+
147
+ begin
148
+ timeout(5) do
149
+ attempts += 1
150
+ client = TCPSocket.new(tcp_client["host"], tcp_client["port"])
151
+ client.print "GET #{light} HTTP/1.0\n\n"
152
+ answer = client.gets(nil)
153
+ STDOUT.puts answer if verbose
154
+ client.close
155
+ end
156
+ rescue Timeout::Error => ex
157
+ STDERR.puts "Error: #{ex} while trying to send #{light}"
158
+ retry unless attempts > 2
159
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => ex
160
+ STDERR.puts "Error: #{ex} while trying to send #{light}"
161
+ STDERR.puts "Will wait for 2 seconds and try again..."
162
+ sleep 2
163
+ retry unless attempts > 2
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ __END__
170
+
171
+ Example payload:
172
+ {
173
+ "name":"test",
174
+ "url":"job/test/",
175
+ "build":{
176
+ "full_url":"http://cronus.local:3001/job/test/20/",
177
+ "number":20,
178
+ "phase":"FINISHED",
179
+ "status":"SUCCESS",
180
+ "url":"job/test/20/"
181
+ }
182
+ }
183
+
184
+ We're getting this error once in a while:
185
+ /usr/local/lib/ruby/1.8/timeout.rb:64:in `notify': execution expired (Timeout::Error)
186
+ from /home/jcmuller/build_notifier/lib/server.rb:102:in `notify'
187
+ from /home/jcmuller/build_notifier/lib/server.rb:33:in `listen'
188
+ from bin/server:5
189
+
@@ -0,0 +1,3 @@
1
+ module BuildStatusServer
2
+ VERSION = "0.4"
3
+ end
@@ -0,0 +1,3 @@
1
+ module BuildStatusServer
2
+ VERSION = "0.4"
3
+ end
@@ -0,0 +1,10 @@
1
+ require "rubygems"
2
+ require "json"
3
+ require "sinatra"
4
+ require "socket"
5
+ require "timeout"
6
+ require "yaml"
7
+
8
+ module BuildStatusServer
9
+ autoload :Server, 'build_status_server/server'
10
+ end
@@ -0,0 +1,11 @@
1
+ require "rubygems"
2
+ require "getoptlong"
3
+ require "json"
4
+ require "sinatra"
5
+ require "socket"
6
+ require "timeout"
7
+ require "yaml"
8
+
9
+ module BuildStatusServer
10
+ autoload :Server, 'build_status_server/server'
11
+ end
@@ -0,0 +1,124 @@
1
+ $:.push File.expand_path("lib", __FILE__)
2
+
3
+ require 'spec_helper'
4
+ require 'build_status_server'
5
+
6
+ describe BuildStatusServer::Server, :pending => true do
7
+ let!(:server) { BuildStatusServer::Server.new(:config => '/dev/null') }
8
+
9
+ describe "#load_config_file" do
10
+ it "should use the supplied argument" do
11
+ config_file = '/dev/null'
12
+ File.should_receive(:exists?).and_return(true)
13
+ server.load_config_file(config_file)
14
+ end
15
+ end
16
+
17
+ describe "#load_store" do
18
+
19
+ before do
20
+ server.stub!(:store_file).and_return("/tmp/build")
21
+ end
22
+
23
+ it "initializes an empty hash if store file doesn't exist" do
24
+ server.load_store
25
+ server.store.should == {}
26
+ end
27
+
28
+ it "initializes an empty hash if store file is empty" do
29
+ require "tempfile"
30
+ f = Tempfile.new("server_spec")
31
+ server.stub!(:store_file).and_return(f.path)
32
+
33
+ server.load_store
34
+ server.store.should == {}
35
+ end
36
+
37
+ it "initializes a hash with the contents of the store file" do
38
+ server.stub!(:store_file).and_return("spec/support/build_result.yml")
39
+ server.load_store
40
+
41
+ server.store.should == {"blah" => "SUCCESS", "test" => "SUCCESS"}
42
+ end
43
+ end
44
+
45
+ describe "#notify"
46
+ describe "#process_all_statuses"
47
+ describe "#process_job"
48
+
49
+ describe "#should_process_build" do
50
+ context "when mask exists" do
51
+ before do
52
+ server.stub!(:mask).and_return(/.*master.*/)
53
+ end
54
+
55
+ context "when policy is include" do
56
+ before do
57
+ server.stub!(:mask_policy).and_return("include")
58
+ end
59
+
60
+ it "ignores builds if mask doesn't match build name" do
61
+ server.should_process_build("blah-development").should be_false
62
+ end
63
+
64
+ it "processes builds if mask matches build name" do
65
+ server.should_process_build("blah-master").should be_true
66
+ end
67
+ end
68
+
69
+ context "when policy is exclude" do
70
+ before do
71
+ server.stub!(:mask_policy).and_return("exclude")
72
+ end
73
+
74
+ it "ignores builds if mask matches build name" do
75
+ server.should_process_build("blah-master").should be_false
76
+ end
77
+
78
+ it "processes builds if mask doesn't match build name" do
79
+ server.should_process_build("blah-development").should be_true
80
+ end
81
+ end
82
+
83
+ context "when policy is undefined" do
84
+ before do
85
+ server.stub!(:mask_policy).and_return(nil)
86
+ end
87
+
88
+ it "ignores builds if mask matches build name" do
89
+ server.should_process_build("blah-master").should be_false
90
+ end
91
+
92
+ it "processes builds if mask doesn't match build name" do
93
+ server.should_process_build("blah-development").should be_true
94
+ end
95
+ end
96
+
97
+ context "when policy is unexpected" do
98
+ before do
99
+ server.stub!(:mask_policy).and_return("trash")
100
+ end
101
+
102
+ it "ignores builds if mask matches build name" do
103
+ server.should_process_build("blah-master").should be_false
104
+ end
105
+
106
+ it "processes builds if mask doesn't match build name" do
107
+ server.should_process_build("blah-development").should be_true
108
+ end
109
+ end
110
+ end
111
+
112
+ context "when mask doesn't" do
113
+ before do
114
+ server.stub!(:mask).and_return(nil)
115
+ end
116
+
117
+ it "should process all jobs" do
118
+ server.should_process_build("blah-development").should be_true
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ # vim:set foldmethod=syntax foldlevel=1:
@@ -0,0 +1,124 @@
1
+ $:.push File.expand_path("lib", __FILE__)
2
+
3
+ require 'spec_helper'
4
+ require 'build_status_server'
5
+
6
+ describe BuildStatusServer::Server do
7
+ let!(:server) { BuildStatusServer::Server.new(:config => '/dev/null') }
8
+
9
+ describe "#load_config_file" do
10
+ it "should use the supplied argument" do
11
+ config_file = '/dev/null'
12
+ File.should_receive(:exists?).and_return(true)
13
+ server.load_config_file(config_file)
14
+ end
15
+ end
16
+
17
+ describe "#load_store" do
18
+
19
+ before do
20
+ server.stub!(:store_file).and_return("/tmp/build")
21
+ end
22
+
23
+ it "initializes an empty hash if store file doesn't exist" do
24
+ server.load_store
25
+ server.store.should == {}
26
+ end
27
+
28
+ it "initializes an empty hash if store file is empty" do
29
+ require "tempfile"
30
+ f = Tempfile.new("server_spec")
31
+ server.stub!(:store_file).and_return(f.path)
32
+
33
+ server.load_store
34
+ server.store.should == {}
35
+ end
36
+
37
+ it "initializes a hash with the contents of the store file" do
38
+ server.stub!(:store_file).and_return("spec/support/build_result.yml")
39
+ server.load_store
40
+
41
+ server.store.should == {"blah" => "SUCCESS", "test" => "SUCCESS"}
42
+ end
43
+ end
44
+
45
+ describe "#notify"
46
+ describe "#process_all_statuses"
47
+ describe "#process_job"
48
+
49
+ describe "#should_process_build" do
50
+ context "when mask exists" do
51
+ before do
52
+ server.stub!(:mask).and_return(/.*master.*/)
53
+ end
54
+
55
+ context "when policy is include" do
56
+ before do
57
+ server.stub!(:mask_policy).and_return("include")
58
+ end
59
+
60
+ it "ignores builds if mask doesn't match build name" do
61
+ server.should_process_build("blah-development").should be_false
62
+ end
63
+
64
+ it "processes builds if mask matches build name" do
65
+ server.should_process_build("blah-master").should be_true
66
+ end
67
+ end
68
+
69
+ context "when policy is exclude" do
70
+ before do
71
+ server.stub!(:mask_policy).and_return("exclude")
72
+ end
73
+
74
+ it "ignores builds if mask matches build name" do
75
+ server.should_process_build("blah-master").should be_false
76
+ end
77
+
78
+ it "processes builds if mask doesn't match build name" do
79
+ server.should_process_build("blah-development").should be_true
80
+ end
81
+ end
82
+
83
+ context "when policy is undefined" do
84
+ before do
85
+ server.stub!(:mask_policy).and_return(nil)
86
+ end
87
+
88
+ it "ignores builds if mask matches build name" do
89
+ server.should_process_build("blah-master").should be_false
90
+ end
91
+
92
+ it "processes builds if mask doesn't match build name" do
93
+ server.should_process_build("blah-development").should be_true
94
+ end
95
+ end
96
+
97
+ context "when policy is unexpected" do
98
+ before do
99
+ server.stub!(:mask_policy).and_return("trash")
100
+ end
101
+
102
+ it "ignores builds if mask matches build name" do
103
+ server.should_process_build("blah-master").should be_false
104
+ end
105
+
106
+ it "processes builds if mask doesn't match build name" do
107
+ server.should_process_build("blah-development").should be_true
108
+ end
109
+ end
110
+ end
111
+
112
+ context "when mask doesn't" do
113
+ before do
114
+ server.stub!(:mask).and_return(nil)
115
+ end
116
+
117
+ it "should process all jobs" do
118
+ server.should_process_build("blah-development").should be_true
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ # vim:set foldmethod=syntax foldlevel=1:
@@ -0,0 +1,15 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ require "rspec/core"
9
+ require "rspec/mocks"
10
+
11
+ RSpec.configure do |config|
12
+ config.treat_symbols_as_metadata_keys_with_true_values = true
13
+ config.run_all_when_everything_filtered = true
14
+ config.filter_run :focus
15
+ end
@@ -0,0 +1,15 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ #require "rspec/core"
9
+ #require "rspec/mocks"
10
+ #
11
+ #RSpec.configure do |config|
12
+ # config.treat_symbols_as_metadata_keys_with_true_values = true
13
+ # config.run_all_when_everything_filtered = true
14
+ # config.filter_run :focus
15
+ #end
@@ -0,0 +1,3 @@
1
+ ---
2
+ blah: SUCCESS
3
+ test: SUCCESS
@@ -0,0 +1,11 @@
1
+ {
2
+ "name":"test-master",
3
+ "url":"job/test/",
4
+ "build":{
5
+ "full_url":"http://cronus.local:3001/job/test/20/",
6
+ "number":20,
7
+ "phase":"FINISHED",
8
+ "status":"SUCCESS",
9
+ "url":"job/test/21/"
10
+ }
11
+ }
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: build_status_server
3
+ version: !ruby/object:Gem::Version
4
+ hash: 3
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 4
9
+ version: "0.4"
10
+ platform: ruby
11
+ authors:
12
+ - Juan C. Muller
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-04-26 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: ruby-debug
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: sinatra
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: json
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :runtime
61
+ version_requirements: *id003
62
+ description: A build notifier server for Jenkins CI that controls an XFD over HTTP
63
+ email: jcmuller@gmail.com
64
+ executables:
65
+ - build_status_server
66
+ extensions: []
67
+
68
+ extra_rdoc_files: []
69
+
70
+ files:
71
+ - lib/build_status_server/requirements.rb~
72
+ - lib/build_status_server/server.rb
73
+ - lib/build_status_server/server.rb~
74
+ - lib/build_status_server/version.rb
75
+ - lib/build_status_server/version.rb~
76
+ - lib/build_status_server.rb
77
+ - lib/build_status_server.rb~
78
+ - spec/lib/build_status_server_spec.rb
79
+ - spec/lib/build_status_server_spec.rb~
80
+ - spec/spec_helper.rb
81
+ - spec/spec_helper.rb~
82
+ - spec/support/build_result.yml
83
+ - spec/support/sample.json
84
+ - bin/build_status_server
85
+ - config/config-example.yml
86
+ - LICENSE
87
+ - README.md
88
+ - Gemfile
89
+ - Gemfile.lock
90
+ - build_status_server.gemspec
91
+ has_rdoc: true
92
+ homepage: http://github.com/jcmuller/build_status_server
93
+ licenses:
94
+ - GPL
95
+ post_install_message:
96
+ rdoc_options: []
97
+
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ hash: 3
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ hash: 3
115
+ segments:
116
+ - 0
117
+ version: "0"
118
+ requirements: []
119
+
120
+ rubyforge_project:
121
+ rubygems_version: 1.6.2
122
+ signing_key:
123
+ specification_version: 3
124
+ summary: A build notifier server for Jenkins CI that controls an XFD over HTTP
125
+ test_files:
126
+ - spec/lib/build_status_server_spec.rb