build_status_server 0.4

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.
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