build_status_server 0.6 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -12,3 +12,7 @@ group :development do
12
12
  gem "ruby-debug"
13
13
  gem "sinatra"
14
14
  end
15
+
16
+ group :development, :test do
17
+ gem "rake"
18
+ end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- build_status_server (0.3)
4
+ build_status_server (0.7)
5
5
  json
6
6
 
7
7
  GEM
@@ -15,6 +15,7 @@ GEM
15
15
  rack (1.4.0)
16
16
  rack-protection (1.2.0)
17
17
  rack
18
+ rake (0.9.2.2)
18
19
  rbx-require-relative (0.0.5)
19
20
  rspec (2.9.0)
20
21
  rspec-core (~> 2.9.0)
@@ -41,6 +42,7 @@ PLATFORMS
41
42
  DEPENDENCIES
42
43
  build_status_server!
43
44
  json
45
+ rake
44
46
  rspec
45
47
  ruby-debug
46
48
  sinatra
@@ -16,11 +16,19 @@ built for my employer ChallengePost (http://challengepost.com). It works in
16
16
  conjunction with our Jenkins Continuous Integration server (and its
17
17
  Notification Plugin)) and an Arduino powered Traffic Light controller
18
18
  (https://github.com/jcmuller/TrafficLightController) with a pseudo-RESTful API.
19
-
20
19
  EOS
21
20
  s.description = "A build notifier server for Jenkins CI that controls an XFD over HTTP"
22
21
 
23
- s.files = Dir["{lib/**/*,spec/**/*}"] + %w(bin/build_status_server config/config-example.yml LICENSE README.md Gemfile Gemfile.lock build_status_server.gemspec)
22
+ s.files = Dir["{lib/**/*,spec/**/*}"] + %w(
23
+ Gemfile
24
+ Gemfile.lock
25
+ LICENSE
26
+ README.md
27
+ bin/build_status_server
28
+ build_status_server.gemspec
29
+ config/config-example.yml
30
+ )
31
+
24
32
  s.require_path = "lib"
25
33
  s.bindir = "bin"
26
34
  s.executables = %w(build_status_server)
@@ -28,6 +36,7 @@ Notification Plugin)) and an Arduino powered Traffic Light controller
28
36
  s.homepage = "http://github.com/jcmuller/build_status_server"
29
37
  s.test_files = Dir["spec/**/*_spec.rb"]
30
38
 
39
+ s.add_development_dependency("rake")
31
40
  s.add_development_dependency("ruby-debug")
32
41
  s.add_development_dependency("sinatra")
33
42
 
@@ -1,14 +1,14 @@
1
1
  udp_server:
2
- address: '127.0.0.1'
2
+ address: 127.0.0.1
3
3
  port: 1234
4
4
  tcp_client:
5
- host: '127.0.0.1'
5
+ host: 127.0.0.1
6
6
  port: 4567
7
- pass: "/green"
8
- fail: "/red"
7
+ pass: /green
8
+ fail: /red
9
9
  store:
10
- filename: "/tmp/build_result.yml"
10
+ filename: /tmp/build_result.yml
11
11
  mask:
12
- regex: ".*master.*"
13
- policy: "include"
14
- verbose: true
12
+ regex: !ruby/regexp /.*master.*/
13
+ policy: include
14
+ verbose: false
@@ -5,5 +5,6 @@ require "timeout"
5
5
  require "yaml"
6
6
 
7
7
  module BuildStatusServer
8
+ autoload :Config, 'build_status_server/config'
8
9
  autoload :Server, 'build_status_server/server'
9
10
  end
@@ -1,10 +1,10 @@
1
1
  require "rubygems"
2
2
  require "json"
3
- require "sinatra"
4
3
  require "socket"
5
4
  require "timeout"
6
5
  require "yaml"
7
6
 
8
7
  module BuildStatusServer
8
+ autoload :Config, 'build_status_server/config'
9
9
  autoload :Server, 'build_status_server/server'
10
10
  end
@@ -0,0 +1,91 @@
1
+ module BuildStatusServer
2
+ class Config
3
+ attr_reader :config
4
+
5
+ def initialize
6
+ @config = {}
7
+ end
8
+
9
+ # This is responsible of loading the config object
10
+ def load(options = {})
11
+ config = load_config_file(options[:config])
12
+ import_config(config, options)
13
+ end
14
+
15
+ def method_missing(meth, *args, &block)
16
+ return config[meth.to_s] if config.has_key?(meth.to_s)
17
+ super
18
+ end
19
+
20
+ def store_file
21
+ return File.expand_path(".", store["filename"]) if store
22
+ nil
23
+ end
24
+
25
+ private
26
+
27
+ # This will load the passed in config object into the config attribute
28
+ def import_config(config = {}, options = {})
29
+ config["verbose"] = options[:verbose] unless options[:verbose].nil?
30
+ @config = config
31
+ end
32
+
33
+ # This is responsible to return a hash with the contents of a YAML file
34
+ def load_config_file(config_file = nil)
35
+ curated_file = nil
36
+
37
+ if config_file
38
+ f = File.expand_path(config_file)
39
+ if File.exists?(f)
40
+ curated_file = f
41
+ else
42
+ raise "Supplied config file (#{config_file}) doesn't seem to exist"
43
+ end
44
+ else
45
+ locations_to_try.each do |possible_conf_file|
46
+ f = File.expand_path(possible_conf_file)
47
+ if File.exists?(f)
48
+ curated_file = f
49
+ break
50
+ end
51
+ end
52
+
53
+ if curated_file.nil?
54
+ STDERR.puts <<-EOT
55
+ Looks like there isn't an available configuration file for this program.
56
+ We're very diligently going to use some sensible defaults, but you're
57
+ strongly recommended to create one in any of the following locations:
58
+
59
+ #{locations_to_try.join("\n ")}
60
+
61
+ Here is a sample of the contents for that file (and the settings we're going
62
+ to use):
63
+
64
+ #{get_example_config}
65
+ EOT
66
+
67
+ return YAML.load(get_example_config)
68
+ end
69
+ end
70
+
71
+ YAML.load_file(curated_file).tap do |config|
72
+ raise "This is an invalid configuration file!" unless config.class == Hash
73
+ end
74
+ end
75
+
76
+ def locations_to_try
77
+ %w(
78
+ ~/.config/build_status_server/config.yml
79
+ ./config/config.yml
80
+ /etc/build_status_server/config.yml
81
+ /usr/local/etc/build_status_server/config.yml
82
+ )
83
+ end
84
+
85
+ def get_example_config
86
+ filename = "#{File.dirname(File.expand_path(__FILE__))}/../../config/config-example.yml"
87
+ File.open(filename).read
88
+ end
89
+ end
90
+ end
91
+
@@ -1,28 +1,18 @@
1
+ # TODO
2
+ # move all configuration stuff to Config
3
+ # and just call config[:blaj] instead of if blah
1
4
  module BuildStatusServer
2
5
  class Server
3
- attr_reader :config, :store_file, :mask_policy, :verbose
4
- attr_accessor :store, :mask
6
+ attr_reader :config, :store
5
7
 
6
8
  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
9
+ @config = Config.new
10
+ config.load(options)
21
11
  end
22
12
 
23
13
  def listen
24
14
  sock = UDPSocket.new
25
- udp_server = config["udp_server"]
15
+ udp_server = config.udp_server
26
16
 
27
17
  begin
28
18
  sock.bind(udp_server["address"], udp_server["port"])
@@ -35,11 +25,11 @@ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
35
25
  exit
36
26
  end
37
27
 
38
- puts "Listening on UDP #{udp_server["address"]}:#{udp_server["port"]}" if verbose
28
+ puts "Listening on UDP #{udp_server["address"]}:#{udp_server["port"]}" if config.verbose
39
29
 
40
30
  while true
41
31
  data, addr = sock.recvfrom(2048)
42
- #require "ruby-debug"; debugger
32
+
43
33
  if process_job(data)
44
34
  status = process_all_statuses
45
35
  notify(status)
@@ -49,13 +39,25 @@ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
49
39
  sock.close
50
40
  end
51
41
 
42
+ private
43
+
44
+ def load_store
45
+ @store = begin
46
+ YAML.load_file(config.store_file)
47
+ rescue
48
+ {}
49
+ end
50
+ @store = {} unless store.class == Hash
51
+ end
52
+
53
+
52
54
  def process_job(data = "{}")
53
55
  job = JSON.parse(data)
54
56
 
55
57
  build_name = job["name"]
56
58
 
57
59
  unless should_process_build(build_name)
58
- STDOUT.puts "Ignoring #{build_name} (#{mask}--#{mask_policy})" if verbose
60
+ STDOUT.puts "Ignoring #{build_name} (#{config.mask["regex"]}--#{config.mask["policy"]})" if config.verbose
59
61
  return false
60
62
  end
61
63
 
@@ -65,77 +67,33 @@ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
65
67
  return false
66
68
  end
67
69
 
68
- phase = job["build"]["phase"]
69
- status = job["build"]["status"]
70
+ phase = job["build"]["phase"]
71
+ status = job["build"]["status"]
70
72
 
71
73
  if phase == "FINISHED"
72
- STDOUT.puts "Got #{status} for #{build_name} on #{Time.now} [#{job.inspect}]" if verbose
74
+ STDOUT.puts "Got #{status} for #{build_name} on #{Time.now} [#{job.inspect}]" if config.verbose
73
75
  case status
74
76
  when "SUCCESS", "FAILURE"
75
77
  load_store
76
78
  store[build_name] = status
77
- File.open(store_file, "w") { |file| YAML.dump(store, file) }
79
+ File.open(config.store_file, "w") { |file| YAML.dump(store, file) }
78
80
  return true
79
81
  end
80
82
  else
81
- STDOUT.puts "Started for #{build_name} on #{Time.now} [#{job.inspect}]" if verbose
83
+ STDOUT.puts "Started for #{build_name} on #{Time.now} [#{job.inspect}]" if config.verbose
82
84
  end
83
85
 
84
86
  return false
85
87
  end
86
88
 
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
89
  def should_process_build(build_name)
136
90
  # If mask exists, then ...
137
- ! (!!mask && ((mask_policy == "include" && build_name !~ mask) ||
138
- (mask_policy != "include" && build_name =~ mask)))
91
+ ! (
92
+ !!config.mask &&
93
+ !!config.mask["regex"] &&
94
+ ((config.mask["policy"] == "include" && build_name !~ config.mask["regex"]) ||
95
+ (config.mask["policy"] != "include" && build_name =~ config.mask["regex"])
96
+ ))
139
97
  end
140
98
 
141
99
  def process_all_statuses
@@ -149,7 +107,7 @@ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
149
107
  end
150
108
 
151
109
  def notify(status)
152
- tcp_client = config["tcp_client"]
110
+ tcp_client = config.tcp_client
153
111
 
154
112
  attempts = 0
155
113
  light = status ? tcp_client["pass"] : tcp_client["fail"]
@@ -160,7 +118,7 @@ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
160
118
  client = TCPSocket.new(tcp_client["host"], tcp_client["port"])
161
119
  client.print "GET #{light} HTTP/1.0\n\n"
162
120
  answer = client.gets(nil)
163
- STDOUT.puts answer if verbose
121
+ STDOUT.puts answer if config.verbose
164
122
  client.close
165
123
  end
166
124
  rescue Timeout::Error => ex
@@ -1,3 +1,3 @@
1
1
  module BuildStatusServer
2
- VERSION = "0.6"
2
+ VERSION = "0.7"
3
3
  end
@@ -1,3 +1,3 @@
1
1
  module BuildStatusServer
2
- VERSION = "0.5"
2
+ VERSION = "0.6"
3
3
  end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+ require 'build_status_server'
3
+ require 'tempfile'
4
+
5
+ describe BuildStatusServer::Config do
6
+ let(:config) { BuildStatusServer::Config.new }
7
+
8
+ describe "#load" do
9
+ it "should call load_config_file with options passed in" do
10
+ options = {:config => nil}
11
+ config.should_receive(:load_config_file).with(options[:config])
12
+ config.load(options)
13
+ end
14
+
15
+ it "should set the config values from yaml file" do
16
+ config.should_receive(:load_config_file).and_return(YAML.load(config.send(:get_example_config)))
17
+ config.load
18
+ config.udp_server.should == {'address' => '127.0.0.1', 'port' => 1234}
19
+ config.verbose.should == false
20
+ end
21
+ end
22
+
23
+ describe "#load_config_file" do
24
+ it "should load the yaml file passed in as a file argument" do
25
+ file_name = nil
26
+
27
+ Tempfile.open('config') do |f|
28
+ f.puts "---"
29
+ f.puts "key: value"
30
+ f.puts "key2: value2"
31
+ file_name = f.path
32
+ end
33
+
34
+ config.send(:load_config_file, file_name).should == {
35
+ "key" => "value",
36
+ "key2" => "value2"
37
+ }
38
+ end
39
+
40
+ it "should try to load paths from the locations to try" do
41
+ file_name = nil
42
+ Tempfile.open("config") do |f|
43
+ f.puts "---"
44
+ f.puts "key: value"
45
+ f.puts "key2: value2"
46
+ file_name = f.path
47
+ end
48
+
49
+ config.stub!(:locations_to_try).and_return([file_name])
50
+ config.send(:load_config_file).should == {
51
+ "key" => "value",
52
+ "key2" => "value2"
53
+ }
54
+ end
55
+
56
+ it "should throw an exception if the config file doesn't exist" do
57
+ file_name = "/tmp/i_dont_exist.yml"
58
+ expect { config.send(:load_config_file, file_name) }.should raise_error RuntimeError, "Supplied config file (#{file_name}) doesn't seem to exist"
59
+ end
60
+
61
+ it "should throw an exception if the config file isn't a hash" do
62
+ file_name = nil
63
+ Tempfile.open(file_name) do |f|
64
+ f.puts "YADDA YADDA"
65
+ file_name = f.path
66
+ end
67
+ expect { config.send(:load_config_file, file_name) }.should raise_error RuntimeError, "This is an invalid configuration file!"
68
+ end
69
+
70
+ it "should return the default options if no default location is found" do
71
+ STDERR.should_receive(:puts)
72
+ config_hash = config.send(:load_config_file)
73
+ config_hash["udp_server"]["address"].should == '127.0.0.1'
74
+ config_hash["verbose"].should == false
75
+ end
76
+ end
77
+
78
+ describe "#store_file" do
79
+ it "returns the store file configured" do
80
+ config.stub!(:store).and_return("filename" => "/tmp/build_result.yml")
81
+ config.store_file.should == "/tmp/build_result.yml"
82
+ end
83
+
84
+ it "returns nil if config doesn't have store option" do
85
+ config.stub!(:store).and_return(nil)
86
+ config.store_file.should be_nil
87
+ end
88
+ end
89
+
90
+ describe "#method_missing" do
91
+ it "should respond to methods named after elements in the config hash" do
92
+ config.send(:import_config, "blah" => 1)
93
+ config.blah.should == 1
94
+ end
95
+
96
+ it "should not respond to methods named after elements that don't exist" do
97
+ expect { config.blah }.should raise_error NoMethodError
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+ require 'build_status_server'
3
+
4
+ describe BuildStatusServer::Server do
5
+ let(:server) { BuildStatusServer::Server.new }
6
+
7
+ before do
8
+ STDERR.should_receive :puts
9
+ end
10
+
11
+ describe "#listen"
12
+
13
+ context "private methods" do
14
+ describe "#load_store" do
15
+ before do
16
+ server.config.stub!(:store_file).and_return("/tmp/build")
17
+ end
18
+
19
+ it "initializes an empty hash if store file doesn't exist" do
20
+ server.send(:load_store)
21
+ server.store.should == {}
22
+ end
23
+
24
+ it "initializes an empty hash if store file is empty" do
25
+ require "tempfile"
26
+ f = Tempfile.new("server_spec")
27
+ server.stub!(:store_file).and_return(f.path)
28
+
29
+ server.send(:load_store)
30
+ server.store.should == {}
31
+ end
32
+
33
+ it "initializes a hash with the contents of the store file" do
34
+ server.config.stub!(:store_file).and_return("spec/support/build_result.yml")
35
+ server.send(:load_store)
36
+
37
+ server.store.should == {"blah" => "SUCCESS", "test" => "SUCCESS"}
38
+ end
39
+ end
40
+
41
+ describe "#notify"
42
+ describe "#process_all_statuses"
43
+ describe "#process_job"
44
+
45
+ describe "#should_process_build" do
46
+ context "when mask exists" do
47
+ before do
48
+ server.stub!(:mask).and_return(%r{.*(?:master).*})
49
+ end
50
+
51
+ context "when policy is include" do
52
+ before do
53
+ server.stub!(:mask_policy).and_return("include")
54
+ end
55
+
56
+ it "ignores builds if mask doesn't match build name" do
57
+ server.send(:should_process_build, "blah-development").should be_false
58
+ end
59
+
60
+ it "processes builds if mask matches build name" do
61
+ server.send(:should_process_build, "blah-master").should be_true
62
+ end
63
+ end
64
+
65
+ context "when policy is exclude" do
66
+ before do
67
+ server.config.stub!(:mask).and_return({"policy" => "exclude", "regex" => /.*(?:master).*/})
68
+ end
69
+
70
+ it "ignores builds if mask matches build name" do
71
+ server.send(:should_process_build, "blah-master").should be_false
72
+ end
73
+
74
+ it "processes builds if mask doesn't match build name" do
75
+ server.send(:should_process_build, "blah-development").should be_true
76
+ end
77
+ end
78
+
79
+ context "when policy is undefined it defaults to ignore" do
80
+ before do
81
+ server.config.stub!(:mask).and_return({"policy" => nil, "regex" => /.*(?:master).*/})
82
+ end
83
+
84
+ it "ignores builds if mask matches build name" do
85
+ server.send(:should_process_build, "blah-master").should be_false
86
+ end
87
+
88
+ it "processes builds if mask doesn't match build name" do
89
+ server.send(:should_process_build, "blah-development").should be_true
90
+ end
91
+ end
92
+
93
+ context "when policy is unexpected it defaults to ignore" do
94
+ before do
95
+ server.config.stub!(:mask).and_return({"policy" => "trash", "regex" => /.*(?:master).*/})
96
+ end
97
+
98
+ it "ignores builds if mask matches build name" do
99
+ server.send(:should_process_build, "blah-master").should be_false
100
+ end
101
+
102
+ it "processes builds if mask doesn't match build name" do
103
+ server.send(:should_process_build, "blah-development").should be_true
104
+ end
105
+ end
106
+ end
107
+
108
+ context "when mask doesn't" do
109
+ before do
110
+ server.config.stub!(:mask).and_return({"policy" => "include", "regex" => nil})
111
+ end
112
+
113
+ it "should process all jobs" do
114
+ server.send(:should_process_build, "blah-development").should be_true
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ # vim:set foldmethod=syntax foldlevel=1:
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: build_status_server
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 5
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 6
9
- version: "0.6"
8
+ - 7
9
+ version: "0.7"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Juan C. Muller
@@ -14,11 +14,10 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2012-04-26 00:00:00 -04:00
18
- default_executable:
17
+ date: 2012-05-01 00:00:00 Z
19
18
  dependencies:
20
19
  - !ruby/object:Gem::Dependency
21
- name: ruby-debug
20
+ name: rake
22
21
  prerelease: false
23
22
  requirement: &id001 !ruby/object:Gem::Requirement
24
23
  none: false
@@ -32,7 +31,7 @@ dependencies:
32
31
  type: :development
33
32
  version_requirements: *id001
34
33
  - !ruby/object:Gem::Dependency
35
- name: sinatra
34
+ name: ruby-debug
36
35
  prerelease: false
37
36
  requirement: &id002 !ruby/object:Gem::Requirement
38
37
  none: false
@@ -46,7 +45,7 @@ dependencies:
46
45
  type: :development
47
46
  version_requirements: *id002
48
47
  - !ruby/object:Gem::Dependency
49
- name: json
48
+ name: sinatra
50
49
  prerelease: false
51
50
  requirement: &id003 !ruby/object:Gem::Requirement
52
51
  none: false
@@ -57,8 +56,22 @@ dependencies:
57
56
  segments:
58
57
  - 0
59
58
  version: "0"
60
- type: :runtime
59
+ type: :development
61
60
  version_requirements: *id003
61
+ - !ruby/object:Gem::Dependency
62
+ name: json
63
+ prerelease: false
64
+ requirement: &id004 !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ type: :runtime
74
+ version_requirements: *id004
62
75
  description: A build notifier server for Jenkins CI that controls an XFD over HTTP
63
76
  email: jcmuller@gmail.com
64
77
  executables:
@@ -68,27 +81,24 @@ extensions: []
68
81
  extra_rdoc_files: []
69
82
 
70
83
  files:
71
- - lib/build_status_server/requirements.rb~
84
+ - lib/build_status_server/config.rb
72
85
  - lib/build_status_server/server.rb
73
- - lib/build_status_server/server.rb~
74
86
  - lib/build_status_server/version.rb
75
87
  - lib/build_status_server/version.rb~
76
88
  - lib/build_status_server.rb
77
89
  - lib/build_status_server.rb~
78
- - spec/lib/build_status_server_spec.rb
79
- - spec/lib/build_status_server_spec.rb~
90
+ - spec/lib/build_status_server/config_spec.rb
91
+ - spec/lib/build_status_server/server_spec.rb
80
92
  - spec/spec_helper.rb
81
- - spec/spec_helper.rb~
82
93
  - spec/support/build_result.yml
83
94
  - spec/support/sample.json
84
- - bin/build_status_server
85
- - config/config-example.yml
86
- - LICENSE
87
- - README.md
88
95
  - Gemfile
89
96
  - Gemfile.lock
97
+ - LICENSE
98
+ - README.md
99
+ - bin/build_status_server
90
100
  - build_status_server.gemspec
91
- has_rdoc: true
101
+ - config/config-example.yml
92
102
  homepage: http://github.com/jcmuller/build_status_server
93
103
  licenses:
94
104
  - GPL
@@ -118,9 +128,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
128
  requirements: []
119
129
 
120
130
  rubyforge_project:
121
- rubygems_version: 1.6.2
131
+ rubygems_version: 1.8.23
122
132
  signing_key:
123
133
  specification_version: 3
124
134
  summary: This utility is part of an XFD (eXtreeme Feedback Device) solution designed and built for my employer ChallengePost (http://challengepost.com). It works in conjunction with our Jenkins Continuous Integration server (and its Notification Plugin)) and an Arduino powered Traffic Light controller (https://github.com/jcmuller/TrafficLightController) with a pseudo-RESTful API.
125
135
  test_files:
126
- - spec/lib/build_status_server_spec.rb
136
+ - spec/lib/build_status_server/config_spec.rb
137
+ - spec/lib/build_status_server/server_spec.rb
@@ -1,8 +0,0 @@
1
- require "rubygems"
2
- require "bundler/setup"
3
-
4
- Bundler.require(:default)
5
-
6
- require "socket"
7
- require "timeout"
8
- require "yaml"
@@ -1,189 +0,0 @@
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
-
@@ -1,124 +0,0 @@
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:
@@ -1,124 +0,0 @@
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:
data/spec/spec_helper.rb~ DELETED
@@ -1,15 +0,0 @@
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